Я изначально разместил это как вопрос только о деструкторах, но теперь я добавляю рассмотрение конструктора по умолчанию. Вот исходный вопрос:
Если я хочу дать моему классу деструктор, который является виртуальным, но в противном случае то же самое, что и для компилятора, я могу использовать =default
:
class Widget {
public:
virtual ~Widget() = default;
};
Но похоже, что я получаю тот же эффект, что и при типизации пустое определение:
class Widget {
public:
virtual ~Widget() {}
};
Есть ли способ, которым эти два определения ведут себя по-другому?
На основе ответов, отправленных для этого вопроса, ситуация для конструктора по умолчанию выглядит схожей. Учитывая, что для деструкторов практически нет разницы в значении между "=default
" и "{}
", аналогично ли разница между этими опциями для конструкторов по умолчанию практически не различается? То есть, предполагая, что я хочу создать тип, в котором объекты такого типа будут созданы и уничтожены, почему я хочу сказать
Widget() = default;
вместо
Widget() {}
?
Извиняюсь, если этот вопрос повторяется после того, как его оригинальная публикация нарушает некоторые правила SO. Публикация почти идентичного вопроса для конструкторов по умолчанию показала мне менее желательный вариант.
Ответ 1
Это совершенно другой вопрос, когда вы спрашиваете о конструкторах, чем деструкторы.
Если ваш деструктор virtual
, то разница незначительна, как указал Говард. Однако, если ваш деструктор не был виртуальным, это совершенно другая история. То же самое относится к конструкторам.
Использование синтаксиса = default
для специальных функций-членов (конструктор по умолчанию, копирование/перемещение конструкторов/назначение, деструкторы и т.д.) означает нечто очень отличное от простого выполнения {}
. С последним функция становится "предоставленной пользователем". И это все меняет.
Это тривиальный класс по определению С++ 11:
struct Trivial
{
int foo;
};
Если вы попытаетесь построить конструкцию по умолчанию, компилятор автоматически сгенерирует конструктор по умолчанию. То же самое касается копирования/перемещения и разрушения. Поскольку пользователь не предоставил ни одну из этих функций-членов, спецификация С++ 11 считает это "тривиальным" классом. Поэтому законно это делать, например, memcpy их содержимое вокруг, чтобы инициализировать их и т.д.
Это:
struct NotTrivial
{
int foo;
NotTrivial() {}
};
Как следует из названия, это уже не тривиально. Он имеет конструктор по умолчанию, который предоставляется пользователю. Не имеет значения, пуст ли он; что касается правил С++ 11, это не может быть тривиальным типом.
Это:
struct Trivial2
{
int foo;
Trivial2() = default;
};
Опять же, как следует из названия, это тривиальный тип. Зачем? Потому что вы сказали компилятору автоматически генерировать конструктор по умолчанию. Поэтому конструктор не является "предоставленным пользователем". И поэтому тип считается тривиальным, поскольку он не имеет предоставленного пользователем конструктора по умолчанию.
Синтаксис = default
в основном используется для выполнения таких задач, как copy constructors/assign, когда вы добавляете функции-члены, которые препятствуют созданию таких функций. Но это также вызывает особое поведение от компилятора, поэтому оно также полезно для конструкторов/деструкторов по умолчанию.
Ответ 2
Они оба нетривиальны.
Оба они имеют одинаковую спецификацию noexcept, в зависимости от отсутствия спецификации баз и членов.
Единственное различие, которое я обнаружил до сих пор, заключается в том, что если Widget
содержит базу или элемент с недоступным или удаленным деструктором:
struct A
{
private:
~A();
};
class Widget {
A a_;
public:
#if 1
virtual ~Widget() = default;
#else
virtual ~Widget() {}
#endif
};
Тогда решение =default
будет компилироваться, но Widget
не будет разрушаемым типом. То есть если вы попытаетесь уничтожить Widget
, вы получите ошибку времени компиляции. Но если вы этого не сделаете, у вас есть рабочая программа.
Otoh, если вы предоставите предоставленный пользователем деструктор, тогда все не будет компилироваться, если вы разрушите Widget
:
test.cpp:8:7: error: field of type 'A' has private destructor
A a_;
^
test.cpp:4:5: note: declared private here
~A();
^
1 error generated.
Ответ 3
Важное различие между
class B {
public:
B(){}
int i;
int j;
};
и
class B {
public:
B() = default;
int i;
int j;
};
заключается в том, что конструктор по умолчанию, определенный с помощью B() = default;
, считается не пользователем. Это означает, что в случае инициализации значения, как в
B* pb = new B(); // use of () triggers value-initialization
особый тип инициализации, который вообще не использует конструктор, и для встроенных типов это приведет к нулевой инициализации. В случае B(){}
это не произойдет. Стандарт С++ n3337 § 8.5/7 говорит
Для инициализации объекта типа типа T означает:
- если T является (возможно, cv-qualified) тип класса (раздел 9) с предоставленным пользователем конструктором(12.1), то конструктор по умолчанию для T называется (и инициализация плохо сформирована, если T не имеет доступного значения по умолчанию конструктор);
- если T является (возможно, cv-квалифицированным) классом типа non-union без предоставленного пользователем конструктора, тогда объект нулевой инициализации и, если Ts неявно объявленный конструктор по умолчанию является нетривиальным, этот конструктор называется.
- если T - тип массива, то каждый элемент инициализируется значением; - в противном случае объект нулевой инициализируется.
Например:
#include <iostream>
class A {
public:
A(){}
int i;
int j;
};
class B {
public:
B() = default;
int i;
int j;
};
int main()
{
for( int i = 0; i < 100; ++i) {
A* pa = new A();
B* pb = new B();
std::cout << pa->i << "," << pa->j << std::endl;
std::cout << pb->i << "," << pb->j << std::endl;
delete pa;
delete pb;
}
return 0;
}
возможный результат:
0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...
http://ideone.com/k8mBrd