c++ - constructores - ¿En qué se diferencia "= default" de "{}" para el constructor y el destructor predeterminados?
copy constructor c++ (3)
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 compilador, 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.
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 compilará, pero el Widget
no será destructible. Es decir, si intenta destruir un Widget
, obtendrá un error en tiempo de compilación. Pero si no lo hace, tiene un programa que funciona.
Otoh, si proporciona el destructor provisto por el usuario , entonces las cosas no se compilará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.
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
sintaxis 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 compilador 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 compilador 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
sintaxis = 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 compilador, por lo que también es útil en los constructores / destructores predeterminados.
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 <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;
}
posible resultado:
0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...