Как "= по умолчанию" отличается от "{}" для конструктора и деструктора по умолчанию?

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

Если я хочу дать моему классу деструктор, который является виртуальным, но в противном случае то же самое, что и для компилятора, я могу использовать =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