Estou muito confuso sobre a inicialização de valor e padrão e zero. e especialmente quando eles são iniciados para os diferentes padrões C ++ 03 e C ++ 11 (e C ++ 14 ).
Estou citando e tentando estender uma resposta realmente boa Valor- / Padrão- / Zero- Init C ++ 98 e C ++ 03 aqui para torná-lo mais geral, pois ajudaria muitos usuários se alguém pudesse ajudar a preencher o lacunas necessárias para ter uma boa visão geral sobre o que acontece quando?
O insight completo por exemplos em poucas palavras:
Às vezes, a memória retornada pelo novo operador será inicializada, e às vezes não, dependendo se o tipo que você está atualizando é um POD (dados antigos simples) , ou se é uma classe que contém membros POD e está usando um construtor padrão gerado pelo compilador.
- Em C ++ 1998 existem 2 tipos de inicialização: zero e default-inicialização
- Em C ++ 2003, um terceiro tipo de inicialização, inicialização de valor foi adicionado.
- Em C ++ 2011 / C ++ 2014, apenas a inicialização de lista foi adicionada e as regras para inicialização de valor / padrão / zero mudaram um pouco.
Presumir:
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 */
Em um compilador C ++ 98, o seguinte deve ocorrer :
new A
- valor indeterminado (A
é POD)new A()
- inicializar zeronew B
- construção padrão (B::m
não inicializada,B
não POD)new B()
- construção padrão (B::m
não inicializada)new C
- construção padrão (C::m
é inicializado com zero,C
não é POD)new C()
- construção padrão (C::m
é inicializado com zero)new D
- construção padrão (D::m
não inicializada,D
não POD)new D()
- construção padrão? (D::m
não foi inicializado)
Em um compilador compatível com C ++ 03, as coisas devem funcionar assim:
new A
- valor indeterminado (A
é POD)new A()
- value-initializeA
, que é inicialização zero, pois é um POD.new B
- inicializa por padrão (deixaB::m
não inicializado,B
não é POD)new B()
- value-initializesB
que inicializa com zero todos os campos, pois seu ctor padrão é gerado pelo compilador em oposição ao definido pelo usuário.new C
- default-initializesC
, que chama o ctor padrão. (C::m
é inicializado com zero,C
não é POD)new C()
- value-initializesC
, que chama o ctor padrão. (C::m
é inicializado com zero)new D
- construção padrão (D::m
não inicializada,D
não POD)new D()
- o valor inicializa D? , que chama o ctor padrão (D::m
não foi inicializado)
Valores em itálico e? são incertezas, por favor ajude a corrigir isso :-)
Em um compilador compatível com C ++ 11, as coisas devem funcionar assim:
??? (por favor me ajude se eu começar aqui, mesmo assim, vai dar errado)
Em um compilador compatível com C ++ 14, as coisas devem funcionar assim: ??? (por favor, ajude se eu começar aqui, de qualquer forma, ele dará errado) (Rascunho com base na resposta)
new A
- inicializa por padrãoA
, compilador gen. ctor, (deixa de serA::m
inicializado) (A
é POD)new A()
- inicializa o valorA
, que é a inicialização de zero desde 2. ponto em [dcl.init] / 8new B
- inicializa por padrãoB
, compilador gen. ctor, (deixa de serB::m
inicializado) (B
não é POD)new B()
- value-initializesB
que inicializa com zero todos os campos, pois seu ctor padrão é gerado pelo compilador em oposição ao definido pelo usuário.new C
- default-initializesC
, que chama o ctor padrão. (C::m
é inicializado com zero,C
não é POD)new C()
- value-initializesC
, que chama o ctor padrão. (C::m
é inicializado com zero)new D
- inicializa por padrãoD
(D::m
não é inicializado,D
não é POD)new D()
- value-initializesD
, que chama o ctor padrão (D::m
não é inicializado)new E
- default-initializesE
, que chama o comp. gen. ctor. (E::m
não foi inicializado, E não é POD)new E()
- inicializa o valorE
, que é inicializado com zeroE
desde 2 pontos em [dcl.init] / 8 )new F
- default-initializesF
, que chama o comp. gen. ctor. (F::m
não foi inicializado,F
não é POD)new F()
- value-initializesF
, que inicializa por padrãoF
desde 1. ponto em [dcl.init] / 8 (aF
função ctor é fornecida pelo usuário se for declarada pelo usuário e não explicitamente padronizada ou excluída em sua primeira declaração. Link )
struct D { D() {}; int m; };
pode valer a pena incluí-lo em sua lista.Respostas:
C ++ 14 especifica a inicialização de objetos criados com
new
em [expr.new] / 17 ([expr.new] / 15 em C ++ 11, e a nota não era uma nota, mas um texto normativo naquela época):A inicialização padrão é definida em [dcl.init] / 7 (/ 6 em C ++ 11, e o próprio texto tem o mesmo efeito):
portanto
new A
apenas faz com queA
o construtor padrão seja chamado, o que não inicializam
. Valor indeterminado. Deve ser o mesmo paranew B
.new A()
é interpretado de acordo com [dcl.init] / 11 (/ 10 em C ++ 11):E agora considere [dcl.init] / 8 (/ 7 em C ++ 11 †):
Portanto,
new A()
será inicializado em zerom
. E isso deve ser equivalente paraA
eB
.new C
enew C()
inicializará o objeto por padrão novamente, já que o primeiro ponto da última citação se aplica (C tem um construtor padrão fornecido pelo usuário!). Mas, claramente, nowm
é inicializado no construtor em ambos os casos.† Bem, este parágrafo tem uma redação ligeiramente diferente em C ++ 11, o que não altera o resultado:
fonte
struct A { int m; }; struct C { C() : m(){}; int m; };
produzir resultados diferentes e o que faz com que m em A seja inicializado em primeiro lugar. Abri um tópico dedicado ao experimento que fiz e agradecerei sua contribuição para esclarecer o problema. Obrigado stackoverflow.com/questions/45290121/…A resposta a seguir estende a resposta https://stackoverflow.com/a/620402/977038 que serviria como uma referência para C ++ 98 e C ++ 03
Citando a resposta
C ++ 11 (em referência a n3242)
Inicializadores
8,5 Initializers [dcl.init] determina que uma variável POD ou não POD pode ser inicializado quer como cinta-ou-igual inicializador que pode ser preparou-init-lista ou inicializador-cláusula no agregado, referido como cinta-ou-igual- inicializador ou usando (lista de expressão) . Antes do C ++ 11, apenas (lista de expressões) ou cláusula inicializadora era suportada, embora a cláusula inicializadora fosse mais restrita do que temos no C ++ 11. No C ++ 11, a cláusula de inicialização agora oferece suporte a lista de inicialização com chaves além da expressão de atribuiçãocomo no C ++ 03. A gramática a seguir resume a nova cláusula suportada, em que a parte em negrito foi adicionada recentemente ao padrão C ++ 11.
inicializador:
chave-ou-igual-inicializador
(lista de expressões)
chave-ou-igual-inicializador:
= cláusula
-inicializável-lista
-inicialização-cláusula-inicial:
expressão-atribuição
-lista-inicial-chave-
inicializador-lista:
cláusula-inicializador ... opt
initializer-list, initializer-clause ... opt **
braced-init-list:
{initializer-list, opt}
{}
Inicialização
Como C ++ 03, C ++ 11 ainda suporta três formas de inicialização
Nota
Tipo de inicializador: 8.5.5 [dcl.init] _zero-initialize_
Executado nos seguintes casos
2. Tipo de inicializador: 8.5.6 [dcl.init] _default-initialize_
Executado nos seguintes casos
3. Tipo de inicializador: 8.5.7 [dcl.init] _value-initialize_
Então, para resumir
fonte
Posso confirmar que em C ++ 11, tudo mencionado na pergunta em C ++ 14 está correto, pelo menos de acordo com as implementações do compilador.
Para verificar isso, adicionei o seguinte código ao meu conjunto de testes . Eu testei
-std=c++11 -O3
no GCC 7.4.0, GCC 5.4.0, Clang 10.0.1 e VS 2017, e todos os testes abaixo foram aprovados.#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(); }
Os locais onde
UB!
é mencionado são comportamentos indefinidos e o comportamento real provavelmente depende de muitos fatores (a.m
pode ser igual a 42, 0 ou algum outro lixo). Os locais onde~UB
é mencionado também são comportamentos indefinidos em teoria, mas na prática, devido ao uso de um posicionamento novo, é muito improvável quea->m
seja igual a qualquer coisa diferente de 42.fonte