Как это & ​​quot; = по умолчанию & quot; отличается от & quot; {} & quot; по умолчанию конструктор и деструктор?

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

Если я хочу дать своему классу деструктор, который является виртуальным, но
в противном случае, то же самое, что генерирует компилятор, я могу использовать =default:

class Widget {
public:
virtual ~Widget() = default;
};

Но похоже, что я могу получить тот же эффект с меньшим набором текста, используя
пустое определение:

class Widget {
public:
virtual ~Widget() {}
};

Есть ли способ, по которому эти два определения ведут себя по-разному?

Судя по ответам на этот вопрос, ситуация для конструктора по умолчанию выглядит аналогично. Учитывая, что практически нет разницы в значении между=default» а также «{}«для деструкторов, есть ли аналогичное различие в значении между этими опциями для конструкторов по умолчанию? То есть, если я хочу создать тип, в котором объекты этого типа будут создаваться и уничтожаться, почему я хотел бы сказать,

Widget() = default;

вместо

Widget() {}

?

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

128

Решение

Это совершенно другой вопрос, когда мы спрашиваем о конструкторах, а не о деструкторах.

Если ваш деструктор 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 Синтаксис в основном используется для создания таких вещей, как копирование конструкторов / присваиваний, когда вы добавляете функции-члены, которые предотвращают создание таких функций. Но это также вызывает специальное поведение компилятора, поэтому оно также полезно в конструкторах / деструкторах по умолчанию.

75

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

Они оба нетривиальны.

Они оба имеют одинаковую спецификацию 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.
37

Важное различие между

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 означает:

— если Т является (возможно
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
//...

http://ideone.com/k8mBrd

28
По вопросам рекламы [email protected]