Чем "=default" отличается от "{}" для конструктора и деструктора по умолчанию?
Я первоначально разместил это как вопрос только о деструкторах, но теперь я добавляю рассмотрение конструктора по умолчанию. Вот оригинальный вопрос:
Если я хочу дать своему классу деструктор, который является виртуальным, но в остальном такой же, как и тот, который генерирует компилятор, я могу использовать
=default
:class Widget { public: virtual ~Widget() = default; };
Но похоже, что я могу получить тот же эффект с меньшим количеством печати, используя пустое определение:
class Widget { public: virtual ~Widget() {} };
Есть ли способ, по которому эти два определения ведут себя по-разному?
Судя по ответам на этот вопрос, ситуация для конструктора по умолчанию выглядит аналогично. Учитывая, что практически нет разницы в значении между=default
" а также "{}
"для деструкторов, есть ли аналогичная разница в значении между этими опциями для конструкторов по умолчанию? То есть, если я хочу создать тип, в котором объекты этого типа будут создаваться и уничтожаться, почему я хотел бы сказать,
Widget() = default;
вместо
Widget() {}
?
Я прошу прощения, если расширение этого вопроса после первоначальной публикации нарушает некоторые правила SO. Размещение почти идентичного вопроса для конструкторов по умолчанию показалось мне менее желательным вариантом.
3 ответа
Это совершенно другой вопрос, когда задаешь конструкторы, а не деструкторы.
Если ваш деструктор virtual
тогда разница незначительна, как отметил Говард. Однако, если ваш деструктор не был виртуальным, это совсем другая история. То же самое верно и для конструкторов.
С помощью = default
Синтаксис для специальных функций-членов (конструктор по умолчанию, конструкторы / присваивания / перемещения, деструкторы и т. д.) означает нечто очень отличное от простого выполнения {}
, С последним функция становится "предоставленной пользователем". И это все меняет.
Это тривиальный класс по определению C++11:
struct Trivial
{
int foo;
};
Если вы попытаетесь создать его по умолчанию, компилятор автоматически сгенерирует конструктор по умолчанию. То же самое касается копирования / перемещения и разрушения. Поскольку пользователь не предоставил ни одну из этих функций-членов, спецификация C++11 считает это "тривиальным" классом. Поэтому это допустимо, например, memcpy их содержимое, чтобы инициализировать их и так далее.
Это:
struct NotTrivial
{
int foo;
NotTrivial() {}
};
Как следует из названия, это уже не тривиально. Он имеет конструктор по умолчанию, предоставленный пользователем. Неважно, если он пуст; Что касается правил C++11, то это не может быть тривиальным типом.
Это:
struct Trivial2
{
int foo;
Trivial2() = default;
};
Опять же, как следует из названия, это тривиальный тип. Зачем? Потому что вы сказали компилятору автоматически генерировать конструктор по умолчанию. Следовательно, конструктор не "предоставляется пользователем". И, следовательно, тип считается тривиальным, поскольку у него нет предоставленного пользователем конструктора по умолчанию.
= default
Синтаксис в основном используется для создания таких вещей, как конструкторы копирования / присваивания, когда вы добавляете функции-члены, которые предотвращают создание таких функций. Но это также вызывает специальное поведение компилятора, поэтому оно также полезно в конструкторах / деструкторах по умолчанию.
Важное различие между
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(){}
этого не произойдет. Стандарт C++ N3337 § 8.5 / 7 говорит
Инициализировать значение объекта типа T означает:
- если T является (возможно, cv-квалифицированным) типом класса (раздел 9) с предоставленным пользователем конструктором (12.1), то вызывается конструктор по умолчанию для T (и инициализация является некорректной, если у T нет доступного конструктора по умолчанию));
- если T является (возможно, cv-квалифицированным) типом класса, не являющимся объединением, без предоставленного пользователем конструктора, то объект инициализируется нулями и, если неявно объявленный конструктор T по умолчанию является нетривиальным, вызывается этот конструктор.
- если 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
//...
Они оба нетривиальны.
Они оба имеют одинаковую спецификацию noexcept в зависимости от спецификации noexcept для баз и членов.
Единственная разница, которую я обнаруживаю, состоит в том, что если Widget
содержит базу или член с недоступным или удаленным деструктором:
struct A
{
private:
~A();
};
class Widget {
A a_;
public:
#if 1
virtual ~Widget() = default;
#else
virtual ~Widget() {}
#endif
};
Тогда =default
решение скомпилируется, но Widget
не будет разрушаемым типом. Т.е. если вы попытаетесь уничтожить Widget
, вы получите ошибку во время компиляции. Но если нет, у вас есть рабочая программа.
Ото, если вы предоставите деструктор, предоставленный пользователем, то вещи не будут компилироваться независимо от того, уничтожаете ли вы 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.