¿En qué se diferencia “= default” de “{}” para el constructor y el destructor predeterminados?

Originalmente publiqué esto como una pregunta solo sobre destructores, pero ahora estoy agregando consideración del constructor predeterminado. Aquí está la pregunta original:

Si quiero dar a mi clase un destructor que sea virtual, pero que sea lo mismo que generaría el comstackdor, puedo usar =default :

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

Pero parece que puedo obtener el mismo efecto con menos tipeo usando una definición vacía:

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

¿Hay alguna forma en que estas dos definiciones se comporten de manera diferente?

Según las respuestas publicadas para esta pregunta, la situación para el constructor predeterminado parece similar. Dado que casi no hay diferencia en el significado entre ” =default ” y ” {} ” para los destructores, ¿no hay casi ninguna diferencia en el significado entre estas opciones para los constructores por defecto? Es decir, suponiendo que quiera crear un tipo en el que los objetos de ese tipo se creen y destruyan, ¿por qué querría decir

 Widget() = default; 

en lugar de

 Widget() {} 

?

Me disculpo si la extensión de esta pregunta después de su publicación original está violando algunas reglas de SO. Publicar una pregunta casi idéntica para los constructores por defecto me pareció la opción menos deseable.

Esta es una pregunta completamente diferente al preguntar sobre constructores que sobre destructores.

Si su destructor es virtual , entonces la diferencia es insignificante, como señaló Howard . Sin embargo, si su destructor no era virtual , es una historia completamente diferente. Lo mismo es cierto para los constructores.

Usar = default syntax por = default para funciones especiales de miembros (constructor predeterminado, copiar / mover constructores / asignación, destructores, etc.) significa algo muy diferente de simplemente hacer {} . Con este último, la función se convierte en “proporcionada por el usuario”. Y eso cambia todo.

Esta es una clase trivial según la definición de C ++ 11:

 struct Trivial { int foo; }; 

Si intenta construir uno por defecto, el comstackdor generará un constructor predeterminado automáticamente. Lo mismo aplica para copia / movimiento y destrucción. Debido a que el usuario no proporcionó ninguna de estas funciones miembro, la especificación C ++ 11 considera esto como una clase “trivial”. Por lo tanto, es legal hacer esto, como memcpy sus contenidos para inicializarlos, etc.

Esta:

 struct NotTrivial { int foo; NotTrivial() {} }; 

Como su nombre lo sugiere, esto ya no es trivial. Tiene un constructor predeterminado proporcionado por el usuario. No importa si está vacío; en lo que respecta a las reglas de C ++ 11, esto no puede ser un tipo trivial.

Esta:

 struct Trivial2 { int foo; Trivial2() = default; }; 

Nuevamente, como su nombre lo sugiere, este es un tipo trivial. ¿Por qué? Porque le dijo al comstackdor que genere automáticamente el constructor predeterminado. El constructor, por lo tanto, no es “proporcionado por el usuario”. Y, por lo tanto, el tipo cuenta como trivial, ya que no tiene un constructor predeterminado proporcionado por el usuario.

La = default syntax = default está principalmente ahí para hacer cosas como copiar constructores / asignación, cuando se agregan funciones miembro que impiden la creación de tales funciones. Pero también desencadena un comportamiento especial del comstackdor, por lo que también es útil en los constructores / destructores predeterminados.

Ambos son no triviales.

Ambos tienen la misma especificación noexcept dependiendo de la especificación noexcept de las bases y los miembros.

La única diferencia que estoy detectando hasta ahora es que si el Widget contiene una base o miembro con un destructor inaccesible o eliminado:

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

Entonces la =default solución =default se comstackrá, pero el Widget no será destructible. Es decir, si intenta destruir un Widget , obtendrá un error en tiempo de comstackción. Pero si no lo hace, tiene un progtwig que funciona.

Otoh, si proporciona el destructor provisto por el usuario , entonces las cosas no se comstackrán ya sea que destruya o no un 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. 

La diferencia importante entre

 class B { public: B(){} int i; int j; }; 

y

 class B { public: B() = default; int i; int j; }; 

es ese constructor predeterminado definido con B() = default; se considera no definido por el usuario . Esto significa que en caso de inicialización de valor como en

 B* pb = new B(); // use of () triggers value-initialization 

se llevará a cabo un tipo especial de inicialización que no use un constructor y para los tipos incorporados esto dará como resultado una inicialización cero . En el caso de B(){} esto no tendrá lugar. El estándar n3337 § 8.5 / 7 de C ++ dice

Valorizar-inicializar un objeto de tipo T significa:

– si T es un tipo de clase (posiblemente cv calificado) (Cláusula 9) con un constructor proporcionado por el usuario (12.1), entonces se llama al constructor predeterminado para T (y la inicialización está mal formada si T no tiene un constructor predeterminado accesible) );

– si T es un tipo de clase no sindicalizada (posiblemente cv calificado) sin un constructor proporcionado por el usuario , entonces el objeto se inicializa en cero y, si el constructor predeterminado implícitamente declarado de T no es trivial, se llama a ese constructor.

– si T es un tipo de matriz, entonces cada elemento tiene un valor inicializado; – de lo contrario, el objeto tiene cero inicialización.

Por ejemplo:

 #include  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; } 

posible resultado:

 0,0 0,0 145084416,0 0,0 145084432,0 0,0 145084416,0 //... 

http://ideone.com/k8mBrd