Suponha que eu tenho uma classe com memebers privadas ptr
, name
, pname
, rname
, crname
e age
. O que acontece se eu não os inicializar? Aqui está um exemplo:
class Example {
private:
int *ptr;
string name;
string *pname;
string &rname;
const string &crname;
int age;
public:
Example() {}
};
E então eu faço:
int main() {
Example ex;
}
Como os membros são inicializados no ex? O que acontece com os ponteiros? Faça string
e int
obtenha 0 inicializado com construtores padrão string()
e int()
? E o membro de referência? E o que dizer das referências const?
Sobre o que mais devo saber?
Alguém conhece um tutorial que cubra esses casos? Talvez em alguns livros? Tenho acesso na biblioteca da universidade a muitos livros em C ++.
Gostaria de aprender para poder escrever programas melhores (sem erros). Qualquer feedback ajudaria!
c++
initialization
member-initialization
bodacydo
fonte
fonte
Respostas:
Em vez de inicialização explícita, a inicialização de membros em classes funciona de forma idêntica à inicialização de variáveis locais em funções.
Para objetos , seu construtor padrão é chamado. Por exemplo, para
std::string
, o construtor padrão o define como uma sequência vazia. Se a classe do objeto não tiver um construtor padrão, será um erro de compilação se você não inicializá-lo explicitamente.Para tipos primitivos (ponteiros, entradas, etc), eles não são inicializados - eles contêm qualquer lixo arbitrário que aconteceu anteriormente naquele local de memória.
Para referências (por exemplo
std::string&
), é ilegal não inicializá-las, e seu compilador irá reclamar e se recusar a compilar esse código. As referências sempre devem ser inicializadas.Portanto, no seu caso específico, se eles não forem explicitamente inicializados:
fonte
foo
tem um construtor, é apenas implícito. Mas isso é realmente um argumento da semântica.Primeiro, deixe-me explicar o que é uma lista de inicializadores de mem . Um mem-inicializador-lista é uma lista separada por vírgulas de mem-inicializador s, onde cada mem-inicializador é um nome membro seguido por
(
, seguido por uma lista de expressão , seguido por um)
. A lista de expressões é como o membro é construído. Por exemplo, ema lista de inicializadores de mem do construtor sem argumentos fornecido pelo usuário
name(s_str, s_str + 8), rname(name), crname(name), age(-4)
. Essa lista de inicializadores de memórias significa que oname
membro é inicializado pelostd::string
construtor que leva dois iterators de entrada , orname
membro é inicializado com uma referência paraname
, ocrname
membro é inicializado com um const-referência paraname
, e oage
membro é inicializado com o valor-4
.Cada construtor tem seu próprio lista de inicializadores de memórias e os membros podem ser inicializados apenas em uma ordem prescrita (basicamente a ordem na qual os membros são declarados na classe). Assim, os membros
Example
só podem ser inicializados na ordem:ptr
,name
,pname
,rname
,crname
, eage
.Quando você não especifica um mem-inicializador de um membro, o padrão C ++ diz:
Aqui porque
name
ser um membro de dados não estáticos do tipo de classe, ele será inicializado por padrão se nenhum inicializador tivername
sido especificado na lista de inicializadores de mem . Todos os outros membros deExample
não têm tipo de classe, portanto, não são inicializados.Quando o padrão diz que eles não foram inicializados, isso significa que eles podem ter qualquer valor. Assim, como o código acima não foi inicializado
pname
, poderia ser qualquer coisa.Observe que você ainda precisa seguir outras regras, como a regra de que as referências sempre devem ser inicializadas. É um erro do compilador não inicializar referências.
fonte
.h
) e a definição (in.cpp
) sem mostrar muitos detalhes internos.Você também pode inicializar membros de dados no ponto em que os declara:
Eu uso esse formulário quase que exclusivamente, embora tenha lido que algumas pessoas o consideram 'ruim', talvez porque tenha sido introduzido apenas recentemente - acho que no C ++ 11. Para mim é mais lógico.
Outra faceta útil para as novas regras é como inicializar membros de dados que são classes. Por exemplo, suponha que
CDynamicString
seja uma classe que encapsule o tratamento de strings. Possui um construtor que permite especificar seu valor inicialCDynamicString(wchat_t* pstrInitialString)
. Você pode muito bem usar essa classe como um membro de dados dentro de outra classe - digamos, uma classe que encapsule um valor de registro do Windows que, nesse caso, armazene um endereço postal. Para 'codificar' o nome da chave do Registro no qual isso grava, você usa chaves:Observe que a segunda classe de string que contém o endereço postal real não possui um inicializador; portanto, seu construtor padrão será chamado na criação - talvez configurando-o automaticamente para uma string em branco.
fonte
Se o exemplo da classe for instanciado na pilha, o conteúdo dos membros escalares não inicializados será aleatório e indefinido.
Para uma instância global, os membros escalares não inicializados serão zerados.
Para membros que são instâncias de classes, seus construtores padrão serão chamados, para que seu objeto string seja inicializado.
int *ptr;
// ponteiro não inicializado (ou zerado se for global)string name;
// construtor chamado, inicializado com uma string vaziastring *pname;
// ponteiro não inicializado (ou zerado se for global)string &rname;
// erro de compilação se você não inicializar issoconst string &crname;
// erro de compilação se você não inicializar issoint age;
// valor escalar, não inicializado e aleatório (ou zerado se for global)fonte
string name
está vazio depois de inicializar a classe na pilha. Você tem certeza absoluta de sua resposta?Membros não estáticos não inicializados conterão dados aleatórios. Na verdade, eles terão apenas o valor da localização da memória à qual estão atribuídos.
Obviamente, para parâmetros do objeto (como
string
), o construtor do objeto poderia fazer uma inicialização padrão.No seu exemplo:
fonte
Membros com um construtor terão seu construtor padrão chamado para inicialização.
Você não pode depender do conteúdo dos outros tipos.
fonte
Se estiver na pilha, o conteúdo de membros não inicializados que não têm seu próprio construtor será aleatório e indefinido. Mesmo que seja global, seria uma má idéia confiar neles como zerados. Esteja na pilha ou não, se um membro tiver seu próprio construtor, isso será chamado para inicializá-lo.
Portanto, se você tiver a string * pname, o ponteiro conterá lixo aleatório. mas para o nome da string, o construtor padrão da string será chamado, fornecendo uma string vazia. Para suas variáveis de tipo de referência, não tenho certeza, mas provavelmente será uma referência a algum pedaço aleatório de memória.
fonte
Depende de como a classe é construída
Responder a essa pergunta vem do entendimento de uma enorme declaração de caso de mudança no padrão da linguagem C ++, e uma que é difícil para os meros mortais obterem intuição.
Como um exemplo simples de como as coisas são difíceis:
main.cpp
Na inicialização padrão, você começaria em: https://en.cppreference.com/w/cpp/language/default_initialization , vamos para a parte "Os efeitos da inicialização padrão são" e iniciamos a instrução case:
Então, se alguém decidir inicializar o valor, vamos para https://en.cppreference.com/w/cpp/language/value_initialization "Os efeitos da inicialização do valor são" e iniciamos a instrução case:
= delete
)É por isso que eu recomendo fortemente que você nunca confie na inicialização zero "implícita". A menos que haja fortes razões de desempenho, inicialize tudo explicitamente, seja no construtor se você definiu um ou usando a inicialização agregada. Caso contrário, você torna as coisas muito arriscadas para futuros desenvolvedores.
fonte