Накладные расходы, обеспечивающие реализацию функции-члена

У меня есть класс Base и класс Derived. Единственная цель класса Base — убедиться, что Derived реализует функцию-член.

struct Base
{
    virtual void f() = 0;
};

struct Derived : Base
{
    void f() override final {}
};

Я не использую этот класс полиморфно, т. е. я просто создаю объекты типа Derived в стеке, например:

Derived obj;

и мне нужно сделать это миллионы раз.

Изменить: одновременно существует только несколько экземпляров (без переполнения стека).

Здесь создается vtable (я думаю, во время компиляции)? Имеет ли для меня значение, создан vtable или нет, поскольку я им не пользуюсь (или каким-то образом)? Есть ли какие-либо накладные расходы, которые я должен учитывать при использовании этого дизайна? Может быть, есть другой способ убедиться, что компилятор жалуется, если Derived не реализует f()?


person mfnx    schedule 17.01.2020    source источник
comment
Отвечает ли это на ваш вопрос? Используется ли final для оптимизации в C++?   -  person Moia    schedule 17.01.2020
comment
Почему бы просто не опустить f() в Base? Если Derived не реализует f(), obj.f() не скомпилируется.   -  person Evg    schedule 17.01.2020
comment
Вы можете проверить размер Derived. В моем случае это было 8 байт, что подразумевает указатель на виртуальную таблицу. Возможно, есть другой способ убедиться, что компилятор жалуется, если Derived не реализует f()? Конечно, если вы вызовете f для любого объекта Derived без указания f, компиляция завершится ошибкой. Также посмотрите: Можно ли написать шаблон для проверки существования функции?. Вы можете написать метафункцию для проверки существования f и использовать ее, например, со статическим утверждением для лучшей диагностики.   -  person Daniel Langr    schedule 17.01.2020
comment
Если вы не используете класс полиморфно, вы можете использовать шаблон CRTP, который а) без дополнительных тайных проходов и свистков обеспечит необходимость реализованного метода б) не использует vtable и может привести к оптимизированным исходящим вызовам   -  person Swift - Friday Pie    schedule 17.01.2020
comment
@Evg хорошее замечание, но представьте, что этот код находится в библиотеке и вызывается только из другой библиотеки.   -  person mfnx    schedule 17.01.2020
comment
@Swift-FridayPie не уверен, как компилятор будет жаловаться на CRTP, если вы не реализуете f() в производном классе.   -  person mfnx    schedule 17.01.2020
comment
Попытка @mfnx static_cast<derived>(this)->f() приведет к ошибке времени компиляции. Существует расширенный способ создания CRTP, который обеспечивает резервную функцию, но не является частью шаблона.   -  person Swift - Friday Pie    schedule 17.01.2020
comment
@Swift Базовым классом будет template ‹typename T› struct Base{...}; Этот компилируется просто отлично. Если я ошибаюсь, напишите ответ с вашим решением. Это будет segfault во время выполнения, а не ошибка компилятора imo.   -  person mfnx    schedule 17.01.2020


Ответы (1)


Создана ли здесь виртуальная таблица?

Да, так как у вас есть virtual функций-членов.

Имеет ли для меня значение, создана виртуальная таблица или нет, поскольку я ее не использую?

Поскольку вы ее не используете, она по-прежнему имеет значение в том смысле, что она увеличит размер вашей структуры Derived.
Здесь ваша структура Derived имеет размер 8. Но без vtable она будет иметь размер 1.

Может быть, есть другой способ убедиться, что компилятор жалуется, если Derived не реализует f()?

Честно говоря, я думаю, что ваше решение, использующее Base в качестве интерфейса, чтобы заставить каждый производный класс реализовать функцию f(), совершенно нормально, поскольку это точный вариант использования интерфейсов.


Но если вас беспокоит размер структуры Derived (потому что вы сказали, что хотите создавать ее экземпляры миллионы раз), возможно, вас заинтересует std::is_member_function_pointer признак типа.

Я понятия не имею, как вы собираетесь создать свою структуру Derived, поэтому я не могу предоставить код, который точно соответствовал бы вашим потребностям.
Но идея, о которой я думаю, эквивалентна следующему (общий пример):

 #include <type_traits>

template <typename T>
void instantiate_a_lot_of_times(std::size_t nb_times)
{
    // Check if the f() member function exists
    static_assert(std::is_member_function_pointer<decltype(&T::f)>::value, "Error: The T::f() member function must be defined");

    for(std::size_t i = 0; i < nb_times; ++i)
    {
        T t;
        // Do something with t
    }
}

Но имейте в виду, что у этого подхода есть недостаток, заключающийся в задержке проверки.
Компиляция завершится ошибкой не при обнаружении определения структуры, а при оценке static_assert.

person Fareanor    schedule 17.01.2020
comment
Я добавил редактирование к своему вопросу: одновременно существует только несколько экземпляров. - person mfnx; 17.01.2020
comment
Я не думаю, что выделение 8 или 1 в стеке важно для производительности, не так ли? - person mfnx; 17.01.2020
comment
@mfnx В этом случае я думаю, что ваш подход должен быть хорошим / хорошим. Я сделал несколько тестов за 10e7 итераций (1 итерация: создание экземпляров 3 Derived объектов и вызов f() для каждого экземпляра). С vtable и без него (размер 8 против размера 1) я не заметил существенной разницы во времени (несколько сотых секунды на моей не очень мощной машине). - person Fareanor; 17.01.2020