Пустые накладные расходы на строительство базового класса?

Рассмотрим следующий код:

#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 объект?

2

Решение

Хотя стандарт не дает никаких гарантий, я бы посчитал компилятор, который сделал что-то другое в этих случаях, слегка неисправным.

Для инициализации базы буквально ничего не нужно делать: не нужно инициализировать память, не нужно настраивать механизм виртуальных вызовов. Код не должен быть сгенерирован для этого.

Тем не менее, вы всегда должны проверять некоторые сборки в нетривиальных условиях, если это действительно важно для вас.

3

Другие решения

Любой ответ на это будет зависеть от реализации, поскольку стандарт определяет только семантику.

Тем не менее, при любом современном компиляторе и включенных оптимизациях я не ожидал бы никакой разницы.

Не нужно выделять дополнительную память, не нужно запускать дополнительный код, нет указателей 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, он даже оптимизирует распределение памяти

2

#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 Например, существует неявно объявленный, тривиальный конструктор по умолчанию, поэтому он не имеет ничего общего.

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector