Будет ли "пустой" конструктор или деструктор делать то же самое, что и сгенерированный?


предположим, что у нас есть класс (toy) C++, например:

class Foo {
    public:
        Foo();
    private:
        int t;
};

поскольку деструктор не определен, компилятор C++ должен создать его автоматически для класса Foo. Если деструктору не нужно очищать какую-либо динамически выделенную память (то есть мы могли бы разумно полагаться на деструктор, который дает нам компилятор), будет определен пустой деструктор, т. е.

Foo::~Foo() { }

сделать то же самое, что компилятор автоматически? Как насчет пустого конструктора -- то есть, Foo::Foo() { }?

если есть различия, то где они существуют? Если не один метод предпочтительнее другого?

7 69

7 ответов:

он будет делать то же самое (ничего, по сути). Но это не то же самое, как если бы вы его не писали. Потому что для записи деструктора потребуется рабочий деструктор базового класса. Если деструктор базового класса является частным или если есть какая-либо другая причина, по которой он не может быть вызван, то ваша программа неисправна. Рассмотрим это

struct A { private: ~A(); };
struct B : A { }; 

это нормально, если вам не требуется разрушать объект типа B (и, следовательно, неявно типа A) - например, если вы никогда не вызываете delete на a динамически созданный объект, или вы никогда не создаете объект из него в первую очередь. Если вы это сделаете, то компилятор отобразит соответствующую диагностику. Теперь, если вы предоставляете один явно

struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } }; 

что один будет пытаться неявно вызвать деструктор базового класса, и вызовет диагностику уже во время определения ~B.

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

struct C;
struct A {
    auto_ptr<C> a;
    A();
};

предположим, что объект типа C создается в определении конструктора A в .cpp файл, который также содержит определение struct C. Теперь, если вы используете структуры A, и требуют уничтожения A объект, компилятор предоставит неявное определение деструктора, как и в случае выше. Этот деструктор также неявно вызовет деструктор объекта auto_ptr. И что удалит указатель, который он держит, что указывает на

Я знаю, что я опаздываю в обсуждении, тем не менее, мой опыт говорит, что компилятор ведет себя по-разному, когда сталкивается с пустой деструктор по сравнению с компилятором автоматически. По крайней мере, это относится к MSVC++ 8.0 (2005) и MSVC++ 9.0 (2008).

при просмотре сгенерированной сборки для некоторого кода, использующего шаблоны выражений, я понял, что в режиме выпуска вызов my BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs) никогда не была подставлена. (пожалуйста, не обращайте внимания на точные типы и оператора подпись.)

для дальнейшей диагностики проблемы, я включил различные предупреждения компилятора, которые отключены по умолчанию. Элемент C4714 предупреждение особенно интересно. Он генерируется компилятором, когда функция помечена __forceinlineне вставляется тем не менее.

я включил предупреждение C4714 и пометил оператора __forceinline и я мог бы проверить отчеты компилятора, что он не смог встроить вызов оператор.

среди причин, описанных в документации, компилятор не может встроить функцию, помеченную __forceinline для:

функции, возвращающие объект unwindable по значению, когда-GX/EHs/EHa включен

это случай моего BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs). BinaryVectorExpression возвращается по значению и даже если его деструктор пуст, это делает это возвращаемое значение рассматривается как объект unwindable. Добавление throw () чтобы деструктор не помог компилятор и я все равно избегаю использования спецификаций исключений. Комментируя пустой деструктор, компилятор может полностью встроить код.

вынос заключается в том, что с этого момента в каждом классе я пишу закомментированные пустые деструкторы, чтобы люди знали, что деструктор ничего не делает специально, точно так же, как люди комментируют пустую спецификацию исключения `/* throw ()*/, чтобы указать, что деструктор не может бросить.

//~Foo() /* throw() */ {}

надеюсь, что помогает.

пустой деструктор, который вы определили вне класса, имеет аналогичную семантику в большинстве случаев, но не во всех.

в частности, неявно определен деструктор
1) является inline публичный член (ваш не встроенный)
2) обозначается как тривиальный деструктор (необходимо сделать тривиальные типы, которые могут быть в объединениях, ваши не могут)
3) имеет спецификацию исключения (throw (), ваш нет)

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

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

virtual ~Foo() { }

отсутствие виртуального деструктора может привести к утечке памяти, потому что люди, которые наследуют от вашего класса Foo, возможно, не заметили, что их деструктор никогда не будет вызван!!

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

пустое определение прекрасно, так как на определение можно ссылаться

virtual ~GameManager() { };
пустое объявление обманчиво похоже по внешнему виду
virtual ~GameManager();
тем не менее приглашает страшный нет определения для виртуального деструктора