Рассмотрим следующий код:
#include <iostream>
#include <type_traits>
class Base
{
public: static int f() {return 42;}
};
class Derived : public Base
{
protected: int x;
};
class NotDerived
{
protected: int x;
};
int main()
{
std::cout<<sizeof(Base)<<std::endl;
std::cout<<sizeof(Derived)<<std::endl;
std::cout<<sizeof(NotDerived)<<std::endl;
return 0;
}
С g ++ 4.7 -O3 он печатает:
1
4
4
и если я хорошо понимаю, это означает, что Оптимизация Пустого Базового Класса включена.
Но мой вопрос касается накладных расходов во время выполнения: есть ли накладные расходы, создающие (и разрушающие) Derived
объект по сравнению с NotDerived
объект в связи с тем, что Derived
должен построить / уничтожить соответствующий Base
объект?
Хотя стандарт не дает никаких гарантий, я бы посчитал компилятор, который сделал что-то другое в этих случаях, слегка неисправным.
Для инициализации базы буквально ничего не нужно делать: не нужно инициализировать память, не нужно настраивать механизм виртуальных вызовов. Код не должен быть сгенерирован для этого.
Тем не менее, вы всегда должны проверять некоторые сборки в нетривиальных условиях, если это действительно важно для вас.
Любой ответ на это будет зависеть от реализации, поскольку стандарт определяет только семантику.
Тем не менее, при любом современном компиляторе и включенных оптимизациях я не ожидал бы никакой разницы.
Не нужно выделять дополнительную память, не нужно запускать дополнительный код, нет указателей vtable, которые можно изменить во время построения, поскольку дополнительная база не является виртуальной. Ваш Derrived
а также NotDerrived
конструкторы вполне могут быть идентичными инструкциям для инструкций.
С отключенной оптимизацией вы можете получить вызов на пустой Base::Base()
на некоторых платформах, но вам не стоит беспокоиться о производительности неоптимизированных сборок.
Я собрал небольшое демо на gcc.godbolt.org: http://tinyurl.com/cg8ogym
Короче
extern void marker______________________________________();
// ...
marker______________________________________();
new NotDerived;
marker______________________________________();
new Derived;
marker______________________________________();
Компилируется в
call marker______________________________________()@PLT
movl $4, %edi
call operator new(unsigned long)@PLT
call marker______________________________________()@PLT
movl $4, %edi
call operator new(unsigned long)@PLT
call marker______________________________________()@PLT
Если вы переключите его на Clang, он даже оптимизирует распределение памяти
#include <type_traits>
#include <unistd.h>
class SlowBase
{
SlowBase() { ::sleep(5); }
public: static int f() {return 42;}
};
static_assert( std::is_empty<SlowBase>::value , "SlowBase is empty" );
Base
класс занимает пять секунд, чтобы построить!!
#include <type_traits>
class FatBase
{
FatBase() = default;
int data[1024*1024];
public: static int f() {return 42;}
};
static_assert( !std::is_empty<FatBase>::value , "FatBase is not empty" );
Но это не займет много времени!
Моя точка зрения заключается в том, что накладные расходы не связаны с размером класса, а с конструктором. делает. SlowBase
это пустой класс, но он очень медленный FatBase
имеет размер в мегабайт, но не обнуляет элементы массива, поэтому не имеет ничего общего.
В вашем Base
Например, существует неявно объявленный, тривиальный конструктор по умолчанию, поэтому он не имеет ничего общего.