Quando é que eu tenho que usar initializer listas para inicializar os membros da classe C ++?

StackOverflow https://stackoverflow.com/questions/1632484

  •  06-07-2019
  •  | 
  •  

Pergunta

Digamos que eu tenho std::map< std::string, std::string > m_someMap como uma variável de membro particular da classe A

Duas perguntas: (ea única razão que eu estou pedindo é porque eu vim através de um código como esse)

  1. Qual é a finalidade desta linha:

    A::A() : m_someMap()
    

    Agora eu sei que este é intialization, mas você tem que fazer isso assim? Estou confuso.

  2. O que é o valor padrão de std::map< std::string, std::string > m_someMap, também C # define que int, double, etc. é sempre inicializados com defualt 0 e objetos devem nula (pelo menos na maioria dos casos) Então, qual é a regra em C ++ ?? são objeto inicializado pelo defualt como nulo e primitivas para o lixo? É claro que eu estou tomando sobre variáveis ??de instância.

EDIT:

Além disso, como a maioria das pessoas apontou que esta é uma escolha de estilo e não é necessário, o que acontece:

A :: A (): m_someMap (), m_someint (0), m_somebool (false)

Foi útil?

Solução

m_somemap

  1. Você não precisa.
  2. O que você ganha se você omiti-lo:. Um std::map< std::string, std::string > vazio, ou seja, uma instância válida desse mapa que não tem elementos nele

m_somebool

  1. Você tem que inicializar-lo para true ou false se você quer ter um valor conhecido. Booleans são "simples tipos de dados antigos" e eles não têm o conceito de um construtor. Além disso, a linguagem C ++ não especificar valores padrão para booleans não explicitamente inicializadas.
  2. O que você ganha se você omiti-lo: um membro booleano com um valor não especificado. Você não deve fazer isso e depois usar o seu valor. Por causa disso, é uma política altamente recomendável que você inicializar todos os valores deste tipo.

m_someint

  1. Você tem que inicializar-lo para algum valor inteiro se você quer ter um valor conhecido. Inteiros são "simples tipos de dados antigos" e eles não têm o conceito de um construtor. Além disso, a linguagem C ++ não especificar valores padrão para inteiros não explicitamente inicializadas.
  2. O que você ganha se você omiti-lo: um membro int com um valor não especificado. Você não deve fazer isso e depois usar o seu valor. Por causa disso, é uma política altamente recomendável que você inicializar todos os valores deste tipo.

Outras dicas

Não há necessidade de realmente fazê-lo.
O construtor padrão irá autmatically fazê-lo.

Mas somtimes, tornando-a explícita ele age como uma espécie de documentação:

class X
{
    std::map<string,string>  data;
    Y                        somePropertyOfdata;

    X()
      :data()                    // Technically not needed
      ,somePropertyOfdata(data)  // But it documents that data is finished construction
    {}                           // before it is used here.
};

A regra em C ++ é que a menos que você explicitamente Inicializar dados POD é indefinido, enquanto outras classes têm lá construtor padrão chamado automaticamente (mesmo que não explicitamente feito pelo programador).

Mas dizer isso. Considere o seguinte:

template<typename T>
class Z
{
     T  data;   
     Z()
        :data()    // Technicall not need as default constructor will
                   // always be called for classes.
                   // But doing this will initialize POD data correctly
                   // if T is a basic POD type. 
     {}
};

Aqui você exepect dados a ser padrão inicializado.
Tecnicamente POD não tem construtores por isso, se T foi int então que você espera que ele faça alguma coisa? Becuase foi explicitamente inicializá-lo é definido como 0 ou o equivalente para tipos POD.

Para a edição:

class A
{
    std::map<string,string>   m_someMap;
    int                       m_someint;
    bool                      m_somebool;
   public:
    A::A()
       : m_someMap()      // Class will always be initialised (so optional)
       , m_someint(0)     // without this POD will be undefined
       , m_somebool(false)// without this POD will be undefined
    {}
};

Como outros salientou: não é necessário, mas mais ou menos uma questão de estilo. A vantagem: ele mostra que você deseja explicitamente usar o construtor padrão e torna seu código mais detalhado. A desvantagem:. Se você tem mais de um ctor pode ser uma dor de manter as alterações em todos eles e às vezes você adicionar membros de classe e se esqueça de adicioná-los aos ctors initializer lista e fazê-lo parecer inconsistente

A::A() : m_someMap()

Esta linha é desnecessário neste caso. No entanto, em geral , é a única maneira adequada para inicializar os membros da classe.

Se você tem um construtor como este:

X() : y(z) {
 w = 42;
}

, em seguida, acontece o seguinte quando o construtor X é chamado:

  • Em primeiro lugar, todos os membros são inicializados: para y, nós explicitamente dizer que deseja chamar o construtor que leva um z como seu argumento. Para w, o que acontece depende do tipo de w. Se w é um tipo POD (isto é, basicamente, um tipo C compatível com: nenhuma herança, nenhum construtor ou destruidores, todos os membros do público, e todos os membros são tipos POD também), então é não inicializado. Seu valor inicial é tudo o lixo foi encontrado nesse endereço de memória. Se w é um tipo não-POD, em seguida, seu construtor padrão é chamado (tipos não-POD são sempre inicializado em construção).
  • Uma vez que ambos os membros foram construídas, nós então chamar o operador de atribuição para atribuir 42 a w.

O importante a notar é que todos os construtores são chamados antes entramos no corpo do construtor. Uma vez que estamos no corpo, todos os membros já foram inicializados. Portanto, há duas possíveis problemas com o nosso corpo construtor.

  • E se w é de um tipo que não tem um construtor padrão? Então isso não vai compilar. Em seguida, ele deve ser explicitamente inicializado após a :, como y é.
  • E se esta sequência de chamar ambos construtor padrão e operador de atribuição é desnecessariamente lento? Talvez seria muito mais eficiente para simplesmente chamar o construtor correto para começar.

Assim, em breve, uma vez m_someMap é um tipo não-POD, nós não, estritamente falando, necessidade de fazer : m_someMap(). Teria sido padrão construído de qualquer maneira. Mas se tivesse sido um tipo POD, ou se quiséssemos chamar outro construtor do que o padrão, então teríamos precisava fazer isso.

Apenas para ser claro sobre o que está acontecendo (com relação à sua segunda pergunta)

std::map< std::string, std::string > m_someMap cria uma variável de pilha chamada m_someMap e o construtor padrão é chamado sobre ele. A regra para C ++ para todos os seus objetos é se você vai:

T varName;

onde T é um tipo, varName é padrão construído.

T* varName;

deve ser explicitamente atribuído a NULL (ou 0) -. Ou nullptr no novo padrão

Para esclarecer a questão valor padrão:

não

C ++ não tem o conceito de alguns implicitamente tipos sendo por referência. A menos que algo seja explicitamente declarado como um ponteiro, ele não pode nunca assumir um valor nulo. Isto significa que todas classe terá um construtor padrão para a construção do valor inicial quando há parâmetros do construtor são especificados. Se nenhum construtor padrão é declarada, o compilador irá gerar um para você. Além disso, sempre que uma classe contém membros que são de tipos de classificados, os membros serão inicializados implicitamente através dos seus próprios construtores padrão na construção do objeto, menos você usar a sintaxe cólon para chamar explicitamente um construtor diferente.

O que acontece é que o construtor padrão para todos os tipos de contêineres STL constrói um recipiente vazio. Outras classes podem ter outras convenções para o que seus construtores padrão fazer, então você ainda quer estar ciente de que eles estão sendo invocado em situações como esta. É por isso que a linha A::A() : m_someMap(), que é realmente apenas dizendo o compilador para fazer o que ele já faria de qualquer maneira.

Quando você cria um objeto em C ++, o construtor passa pela seguinte seqüência:

  1. Chame os construtores de todas as classes virtual pai em toda a árvore de classe (em uma ordem arbitrária)
  2. Chame os construtores de todas as classes pai diretamente herdadas na ordem de declaração
  3. Chame os construtores de todas as variáveis ??de membro da ordem de declaração

Existem mais algumas especificidades do que isso, e alguns compiladores permitem forçar algumas coisas fora desta ordem específica, mas esta é a idéia geral. Para cada uma destas chamadas do construtor você pode especificar os argumentos do construtor, caso em que C ++ irá chamar o construtor como previsto, ou você pode deixá-lo sozinho e C ++ irá tentar chamar o construtor padrão. O construtor padrão é simplesmente aquele que não tem argumentos.

Se qualquer de suas classes virtual pai, classes pai não-virtuais, ou variáveis ??de membro não tem um construtor padrão ou necessidade de ser criado com algo diferente do padrão, você adicioná-los à lista de chamadas do construtor. Porque C ++ assume uma chamada de construtor padrão, não há absolutamente nenhuma diferença entre colocar um construtor padrão na lista e deixá-lo por completo (C ++ não vai (a não ser em circunstâncias especiais fora do âmbito desta questão) criar um objeto sem uma chamada para um construtor de algum tipo). Se a classe não tem um construtor padrão e você não fornecer uma chamada de construtor, o compilador irá lançar um erro.

Quando se trata de construído em tipos, tais float ou int, o construtor padrão não faz nada em tudo, e por isso a variável terá o valor padrão de tudo o que foi deixado na parte da memória. Tudo construído em tipos também têm um construtor de cópia para que você possa você pode inicializar-los, passando o seu valor inicial como o único argumento ao construtor da variável.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top