Чим "= default" відрізняється від "{}" для конструктора та деструктора за замовчуванням?


169

Спочатку я розміщував це як питання лише про деструктори, але тепер додаю розгляд конструктора за замовчуванням. Ось оригінальне запитання:

Якщо я хочу дати своєму класу деструктор, який є віртуальним, але інакше такий же, як і генерував компілятор, я можу використовувати =default:

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

Але здається, що я можу отримати той же ефект при меншому наборі тексту, використовуючи порожнє визначення:

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

Чи є спосіб, коли ці два визначення поводяться по-різному?

На основі відповідей, розміщених на це питання, ситуація для конструктора за замовчуванням здається подібною. З огляду на те, що для деструкторів майже немає різниці в значенні між " =default" і " {}", чи не існує майже однакової різниці в значенні між цими параметрами для конструкторів за замовчуванням? Тобто, припускаючи, що я хочу створити тип, де об’єкти цього типу будуть створюватися і знищуватися, чому я б хотів сказати

Widget() = default;

замість

Widget() {}

?

Прошу вибачення, якщо продовження цього питання після його оригінальної публікації порушує деякі правила ТА. Повідомлення майже однакового питання для конструкторів за замовчуванням вважало мене менш бажаним варіантом.


1
Не те, про що я знаю, але = defaultє більш явним imo, і відповідає підтримці його з конструкторами.
chris

11
Я точно не знаю, але я думаю, що перший відповідає визначенню "тривіальний руйнівник", а другий - ні. Так std::has_trivial_destructor<Widget>::valueє trueдля першого, але falseдля другого. Які наслідки цього я не знаю. :)
GManNickG

10
Віртуальний деструктор ніколи не є банальним.
Люк Дантон

@LucDanton: Я гадаю, що відкрити очі і подивитися на код теж би спрацювало! Дякуємо за виправлення.
GManNickG

Відповіді:


103

Це зовсім інше питання, коли задають питання про конструктори, ніж про деструктори.

Якщо ваш деструктор є virtual, то різниця незначна, як вказував Говард . Однак якщо ваш деструктор був невіртуальним , це зовсім інша історія. Те саме стосується конструкторів.

Використання = defaultсинтаксису для спеціальних функцій-членів (конструктор за замовчуванням, конструктори копіювання / переміщення / призначення, деструктори тощо) означає щось дуже відрізняється від простого виконання {}. З останнім функція стає "наданою користувачем". І це все змінює.

Це тривіальний клас за визначенням C ++ 11:

struct Trivial
{
  int foo;
};

Якщо ви спробуєте конструювати його за замовчуванням, компілятор автоматично створить конструктор за замовчуванням. Те саме стосується копіювання / руху та руйнування. Оскільки користувач не надав жодної з цих функцій-членів, специфікація C ++ 11 вважає цей клас "тривіальним". Тому законно це робити, як, наприклад, запомнювати їхній вміст, щоб ініціалізувати їх тощо.

Це:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

Як випливає з назви, це вже не банально. Він має конструктор за замовчуванням, який надається користувачем. Не має значення, чи порожній він; що стосується правил C ++ 11, це не може бути тривіальним типом.

Це:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

Знову ж, як випливає з назви, це тривіальний тип. Чому? Тому що ви сказали компілятору автоматично генерувати конструктор за замовчуванням. Отже, конструктор не надається користувачем. Отже, тип вважається тривіальним, оскільки він не має наданого користувачем конструктора за замовчуванням.

= defaultСинтаксис в основному там робити такі речі , як конструктори копіювання / присвоювання, коли ви додаєте функції - членів , які перешкоджають створенню таких функцій. Але це також викликає особливу поведінку компілятора, тому це корисно і для конструкторів / деструкторів за замовчуванням.


2
Отже, ключовим питанням здається, чи є отриманий клас тривіальним, і в основі цього питання лежить різниця між спеціальною функцією, яка оголошується користувачем (що стосується =defaultфункцій) і наданими користувачем (що стосується {}) функцій. Як декларовані користувачем, так і призначені користувачем функції можуть перешкоджати генерації інших функцій спеціального члена (наприклад, оголошений користувачем деструктор перешкоджає генерації операцій переміщення), але лише спеціальна функція, що надається користувачем, робить клас нетривіальним. Правильно?
KnowItAllWannabe

@KnowItAllWannabe: Це загальна ідея, так.
Ніколь Болас

Я обираю це як прийняту відповідь, лише тому, що вона охоплює як конструктори, так і (посилаючись на відповідь Говарда) деструктори.
KnowItAllWannabe

Здається, тут відсутні слова "що стосується правил C ++ 11, ви маєте права тривіального типу" Я це виправлю, але я не впевнений на 100% впевненість, що було призначено.
jcoder

2
= defaultвидається корисним для примушення компілятора до сформованого конструктора за замовчуванням, незважаючи на наявність інших конструкторів; конструктор за замовчуванням неявно не оголошується, якщо надаються будь-які інші оголошені користувачем конструктори.
bgfvdu3w

42

Вони обидва нетривіальні.

Вони обидва мають однакові специфікації noexcept, залежно від специфікації noexcept бази та елементів.

Єдина відмінність, яку я зараз виявляю, полягає в тому, що якщо вона Widgetмістить базу або член з недоступним або видаленим деструктором:

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

class Widget {
    A a_;
public:
#if 1
   virtual ~Widget() = default;
#else
   virtual ~Widget() {}
#endif
};

Тоді =defaultрішення збирається, але Widgetне буде руйнівним типом. Тобто, якщо ви спробуєте знищити a 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.

9
Цікаво: інакше кажучи, =default;компілятор не генерує деструктор, якщо він не використовується, і тому не спровокує помилку. Мені це здається дивним, навіть якщо це не обов'язково помилка. Я не можу уявити, що така поведінка передбачена у стандарті.
Нік Бугаліс

"Тоді рішення = за замовчуванням буде компілювати" Ні, це не буде. Щойно перевірено в порівнянні з
нано

Що було повідомлення про помилку та яка версія VS?
Говард Хінант

35

Важлива різниця між

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
//...

http://ideone.com/k8mBrd


Тоді чому "{}" і "= за замовчуванням" завжди ініціалізують std :: string ideone.com/LMv5Uf ?
nawfel bgh

1
@nawfelbgh Конструктор за замовчуванням A () {} викликає конструктор за замовчуванням для std :: string, оскільки це не тип POD. За замовчуванням ctor std :: string ініціалізує його до порожнього, 0 розмірного рядка. Цтор за замовчуванням для скалярів не робить нічого: об'єкти з автоматичною тривалістю зберігання (та їх субекти) ініціалізуються до невизначених значень.
4pie0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.