Estoy muy confundido sobre el valor, el valor predeterminado y la inicialización cero. y especialmente cuando entran en juego para los diferentes estándares C ++ 03 y C ++ 11 (y C ++ 14 ).
Estoy citando y tratando de extender una respuesta realmente buena Value- / Default- / Zero- Init C ++ 98 y C ++ 03 aquí para hacerlo más general, ya que ayudaría a muchos usuarios si alguien pudiera ayudar a completar el los espacios necesarios para tener una buena visión general de lo que sucede cuando?
La visión completa mediante ejemplos en pocas palabras:
A veces, la memoria devuelta por el nuevo operador se inicializará y, a veces, no dependerá de si el tipo que está creando es un POD (datos antiguos sin formato) o si es una clase que contiene miembros de POD y está usando un constructor predeterminado generado por el compilador.
- En C ++ 1998 hay 2 tipos de inicialización: inicialización cero y por defecto
- En C ++ 2003 se agregó un tercer tipo de inicialización, inicialización de valor .
- En C ++ 2011 / C ++ 2014, solo se agregó la inicialización de lista y las reglas para la inicialización de valor / predeterminado / cero cambiaron un poco.
Asumir:
struct A { int m; };
struct B { ~B(); int m; };
struct C { C() : m(){}; ~C(); int m; };
struct D { D(){}; int m; };
struct E { E() = default; int m;}; /** only possible in c++11/14 */
struct F {F(); int m;}; F::F() = default; /** only possible in c++11/14 */
En un compilador de C ++ 98, debería ocurrir lo siguiente :
new A
- valor indeterminado (A
es POD)new A()
- inicializar ceronew B
- construcción predeterminada (B::m
no está inicializada,B
no es POD)new B()
- construcción predeterminada (B::m
no está inicializada)new C
- construcción predeterminada (C::m
se inicializa a cero,C
no es POD)new C()
- construcción predeterminada (C::m
se inicializa a cero)new D
- construcción predeterminada (D::m
no está inicializada,D
no es POD)new D()
- construcción predeterminada? (D::m
no está inicializado)
En un compilador conforme a C ++ 03, las cosas deberían funcionar así:
new A
- valor indeterminado (A
es POD)new A()
- value-initializeA
, que es una inicialización cero ya que es un POD.new B
- default-initializes (deja sinB::m
inicializar,B
no es POD)new B()
- valor-inicializaB
que cero todos los campos, ya que su ctor predeterminado es generado por el compilador en lugar de definido por el usuario.new C
- default-initializesC
, que llama al ctor predeterminado. (C::m
está inicializado a cero,C
no es POD)new C()
- value-initializesC
, que llama al ctor predeterminado. (C::m
está inicializado a cero)new D
- construcción predeterminada (D::m
no está inicializada,D
no es POD)new D()
- valor-inicializa D? , que llama al ctor predeterminado (D::m
no está inicializado)
Valores en cursiva y? son incertidumbres, por favor ayude a corregir esto :-)
En un compilador conforme a C ++ 11, las cosas deberían funcionar así:
??? (por favor ayúdenme si empiezo aquí, de todos modos saldrá mal)
En un compilador compatible con C ++ 14, las cosas deberían funcionar así: ??? (por favor ayude si empiezo aquí, de todos modos saldrá mal )
new A
- default-initializesA
, compilador gen. ctor, (deja sinA::m
inicializar) (A
es POD)new A()
- valor-inicializaA
, que es cero-inicialización desde 2. punto en [dcl.init] / 8new B
- default-initializesB
, compilador gen. ctor, (deja sinB::m
inicializar) (B
no es POD)new B()
- value-initializes,B
que inicializa a cero todos los campos, ya que su ctor predeterminado es generado por el compilador en lugar de definido por el usuario.new C
- default-initializesC
, que llama al ctor predeterminado. (C::m
está inicializado a cero,C
no es POD)new C()
- value-initializesC
, que llama al ctor predeterminado. (C::m
está inicializado a cero)new D
- default-initializesD
(D::m
no está inicializado,D
no es POD)new D()
- value-initializesD
, que llama al ctor predeterminado (D::m
no está inicializado)new E
- default-initializesE
, que llama a comp. gen. ctor. (E::m
no está inicializado, E no es POD)new E()
- value-initializesE
, que se inicializa a ceroE
desde el punto 2 en [dcl.init] / 8 )new F
- default-initializesF
, que llama a comp. gen. ctor. (F::m
no está inicializado,F
no es POD)new F()
- value-initializesF
, que se inicializa por defectoF
desde 1. punto en [dcl.init] / 8 (laF
función ctor es proporcionada por el usuario si está declarada por el usuario y no está explícitamente predeterminada o eliminada en su primera declaración. Enlace )
struct D { D() {}; int m; };
puede valer la pena incluirlo en su lista.Respuestas:
C ++ 14 especifica la inicialización de objetos creados con
new
[expr.new] / 17 ([expr.new] / 15 en C ++ 11, y la nota no era una nota sino texto normativo en ese entonces):La inicialización predeterminada se define en [dcl.init] / 7 (/ 6 en C ++ 11, y la redacción en sí tiene el mismo efecto):
Así
new A
únicamente haceA
que se llame al constructor predeterminado s, que no se inicializam
. Valor indeterminado. Debería ser el mismo paranew B
.new A()
se interpreta de acuerdo con [dcl.init] / 11 (/ 10 en C ++ 11):Y ahora considere [dcl.init] / 8 (/ 7 en C ++ 11 †):
Por
new A()
lo tanto, se inicializará a cerom
. Y esto debería ser equivalente aA
yB
.new C
ynew C()
volverá a inicializar el objeto por defecto, ya que se aplica el primer punto de la última cita (¡C tiene un constructor predeterminado proporcionado por el usuario!). Pero, claramente, ahoram
se inicializa en el constructor en ambos casos.† Bueno, este párrafo tiene una redacción ligeramente diferente en C ++ 11, lo que no altera el resultado:
fuente
struct A { int m; }; struct C { C() : m(){}; int m; };
producir resultados diferentes y qué causa que m en A se inicialice en primer lugar. Abrí un hilo dedicado para el experimento que hice y agradeceré su contribución para aclarar el problema. Gracias stackoverflow.com/questions/45290121/…La siguiente respuesta extiende la respuesta https://stackoverflow.com/a/620402/977038 que serviría como referencia para C ++ 98 y C ++ 03
Citando la respuesta
C ++ 11 (en referencia a n3242)
Inicializadores
8.5 Inicializadores [dcl.init] especifica que un POD variable o no POD se puede inicializar como inicializador de llave o igual, que puede ser una lista de inicialización con llaves o una cláusula de inicialización denominada de forma agregada como llave o igual. inicializador o usando (lista de expresiones) . Antes de C ++ 11, solo se admitía (lista-expresión) o cláusula-inicializador, aunque la cláusula-inicializador era más restringida que la que tenemos en C ++ 11. En C ++ 11, initializer-clause ahora admite la lista de inicialización con llaves además de la expresión de asignacióncomo en C ++ 03. La siguiente gramática resume la nueva cláusula admitida, donde la parte en negrita se agregó recientemente en el estándar C ++ 11.
inicializador: inicializador
-llave-o-igual
(lista-expresión) inicializador -
llave-o-igual:
= cláusula
-inicializador-lista
-inicial- llave- inicial-cláusula - inicialización:
expresión-asignación
-lista
-inicial-llaves-lista
-inicializador : cláusula-inicializador ... opt
lista-inicializador, cláusula-inicializador ... opt ** lista-inicial-entre
-llaves:
{lista-inicializador, opt}
{}
Inicialización
Como C ++ 03, C ++ 11 todavía admite tres formas de inicialización
Nota
Tipo de inicializador: 8.5.5 [dcl.init] _zero-initialize_
Realizado en los siguientes casos
2. Tipo de inicializador: 8.5.6 [dcl.init] _default-initialize_
Realizado en los siguientes casos
3. Tipo de inicializador: 8.5.7 [dcl.init] _value-initialize_
Así que para resumir
fuente
Puedo confirmar que en C ++ 11, todo lo mencionado en la pregunta en C ++ 14 es correcto, al menos según las implementaciones del compilador.
Para verificar esto, agregué el siguiente código a mi conjunto de pruebas . Probé con
-std=c++11 -O3
en GCC 7.4.0, GCC 5.4.0, 10.0.1 Clang, y VS 2017, y todos los ensayos siguientes pase.#include <gtest/gtest.h> #include <memory> struct A { int m; }; struct B { int m; ~B(){}; }; struct C { int m; C():m(){}; ~C(){}; }; struct D { int m; D(){}; }; struct E { int m; E() = default; }; struct F { int m; F(); }; F::F() = default; // We use this macro to fill stack memory with something else than 0. // Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but // pass in practice, and help illustrate that `a.m` is indeed not initialized // to zero. Note that we initially tried the more aggressive test // EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to // 42, but was still equal to some garbage value, not zero). // #define FILL { int m = 42; EXPECT_EQ(m, 42); } // We use this macro to fill heap memory with something else than 0, before // doing a placement new at that same exact location. Subsequent calls to // EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice, // and help illustrate that `a->m` is indeed not initialized to zero. // #define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42) TEST(TestZero, StackDefaultInitialization) { { FILL; A a; EXPECT_NE(a.m, 0); } // UB! { FILL; B a; EXPECT_NE(a.m, 0); } // UB! { FILL; C a; EXPECT_EQ(a.m, 0); } { FILL; D a; EXPECT_NE(a.m, 0); } // UB! { FILL; E a; EXPECT_NE(a.m, 0); } // UB! { FILL; F a; EXPECT_NE(a.m, 0); } // UB! } TEST(TestZero, StackValueInitialization) { { FILL; A a = A(); EXPECT_EQ(a.m, 0); } { FILL; B a = B(); EXPECT_EQ(a.m, 0); } { FILL; C a = C(); EXPECT_EQ(a.m, 0); } { FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB! { FILL; E a = E(); EXPECT_EQ(a.m, 0); } { FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB! } TEST(TestZero, StackListInitialization) { { FILL; A a{}; EXPECT_EQ(a.m, 0); } { FILL; B a{}; EXPECT_EQ(a.m, 0); } { FILL; C a{}; EXPECT_EQ(a.m, 0); } { FILL; D a{}; EXPECT_NE(a.m, 0); } // UB! { FILL; E a{}; EXPECT_EQ(a.m, 0); } { FILL; F a{}; EXPECT_NE(a.m, 0); } // UB! } TEST(TestZero, HeapDefaultInitialization) { { FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0); } { FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB } TEST(TestZero, HeapValueInitialization) { { FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0); } { FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0); } { FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0); } { FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0); } { FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB } TEST(TestZero, HeapListInitialization) { { FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0); } { FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0); } { FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0); } { FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0); } { FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
Los lugares donde
UB!
se menciona son comportamientos indefinidos, y es probable que el comportamiento real dependa de muchos factores (a.m
podría ser igual a 42, 0 o alguna otra basura). Los lugares donde~UB
se menciona también son comportamientos indefinidos en teoría, pero en la práctica, debido al uso de una ubicación nueva, es muy poco probable quea->m
sea igual a otra cosa que 42.fuente