Будет 'пустой' конструктор или деструктор делают то же самое как сгенерированное?

Ради любви к $ DEITY, пожалуйста, не делайте функцию сна с занятым ожиданием. setTimeout и setInterval делают все, что вам нужно.

72
задан 眠りネロク 11 September 2018 в 13:57
поделиться

7 ответов

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

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

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

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

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

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

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

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

На самом деле это обычная проблема при реализации идиомы pimpl. Решением здесь является добавление деструктора и предоставление его пустого определения в файле .cpp , где определена структура C . В то время, когда он вызывает деструктор своего члена, он будет знать определение struct C и сможет правильно вызвать его деструктор.

struct C;
struct A {
    auto_ptr<C> a;
    A();
    ~A(); // defined as ~A() { } in .cpp file, too
};

Обратите внимание, что boost :: shared_ptr не имеет этой проблемы: вместо этого требуется полный тип, когда его конструктор вызывается определенными способами.

Другой момент, в котором это имеет значение в текущем C ++, - это когда вы хотите использовать memset и его друзей для такого объекта, у которого есть объявленный пользователем деструктор. Такие типы больше не являются POD (простые старые данные), и их нельзя копировать побитно. Обратите внимание, что это ограничение на самом деле не требуется - и следующая версия C ++ улучшила ситуацию в этом отношении, так что она позволяет вам по-прежнему копировать такие типы в битах до тех пор, пока не будут внесены другие более важные изменения.


Поскольку вы просили о конструкторах: Что ж, в этом смысле то же самое. Обратите внимание, что конструкторы также содержат неявные вызовы деструкторов. В таких вещах, как auto_ptr, эти вызовы (даже если они фактически не выполняются во время выполнения - чистая возможность здесь уже имеет значение) нанесут тот же вред, что и для деструкторов, и произойдут, когда что-то в конструкторе выбрасывает - компилятор затем должен вызвать деструктор членов. В этом ответе используется неявное определение конструкторов по умолчанию.

Также, то же самое верно для видимости и PODness, что я сказал о деструкторе выше.

Есть одно важное различие относительно инициализации. Если вы поместите объявленный пользователем конструктор, ваш тип больше не получит инициализацию значений членов, и ваш конструктор должен выполнить любую необходимую инициализацию. Пример:

struct A {
    int a;
};

struct B {
    int b;
    B() { }
};

В этом случае всегда верно следующее

assert(A().a == 0);

В то время как следующее является неопределенным поведением, потому что b никогда не инициализировался (ваш конструктор это пропустил). Значение может быть нулем, но также может быть любое другое странное значение. Попытка чтения из такого неинициализированного объекта вызывает неопределенное поведение.

assert(B().b == 0);

Это также верно для использования этого синтаксиса в new , например new A () (обратите внимание на круглые скобки в конце - если они опущены, инициализация значения не выполняется,

116
ответ дан 24 November 2019 в 12:37
поделиться

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

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

12
ответ дан 24 November 2019 в 12:37
поделиться

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

8
ответ дан 24 November 2019 в 12:37
поделиться

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

virtual ~Foo() { }

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

3
ответ дан 24 November 2019 в 12:37
поделиться

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

1
ответ дан 24 November 2019 в 12:37
поделиться

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

virtual ~GameManager() { };
Пустое объявление обманчиво похоже на вид
virtual ~GameManager();
, но предлагает ужасное отсутствие определения для виртуального деструктора ошибка
Undefined symbols:
  "vtable for GameManager", referenced from:
      __ZTV11GameManager$non_lazy_ptr in GameManager.o
      __ZTV11GameManager$non_lazy_ptr in Main.o
ld: symbol(s) not found
0
ответ дан 24 November 2019 в 12:37
поделиться

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

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

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

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

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

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

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

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

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

Надеюсь, это поможет.

18
ответ дан 24 November 2019 в 12:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: