Por que devo preferir usar a lista de inicialização de membros?

Respostas:

278

Para os alunos da classe POD , não faz diferença, é apenas uma questão de estilo. Para membros da classe que são classes, evita-se uma chamada desnecessária para um construtor padrão. Considerar:

class A
{
public:
    A() { x = 0; }
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};

Nesse caso, o construtor for Bchamará o construtor padrão para Ae inicializará a.xpara 3. Uma maneira melhor seria que Bo construtor chamasse diretamente Ao construtor na lista de inicializadores:

B()
  : a(3)
{
}

Isso chamaria apenas Ao A(int)construtor e não o construtor padrão. Neste exemplo, a diferença é insignificante, mas imagine se você quiser A, o construtor padrão fez mais, como alocar memória ou abrir arquivos. Você não gostaria de fazer isso desnecessariamente.

Além disso, se uma classe não tiver um construtor padrão ou se você tiver uma constvariável de membro, deverá usar uma lista de inicializadores:

class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};
Adam Rosenfield
fonte
5
uma obrigação também é para caso importante de uma referência
4pie0
5
Por que não usar "a (3);" ou "a = A (3);" no corpo do construtor padrão de B?
Sergey
1
Você poderia explicar o que você quer dizer com POD?
Jonas Stein
2
O @JonasStein POD é um conjunto bem definido de regras relativas a estruturas de dados simples (em vez de classes completas). Leia o FAQ para mais: stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506
2
@ Emery, o construtor padrão do A ainda seria chamado.
Vassilis
44

Além dos motivos de desempenho mencionados acima, se sua classe armazena referências a objetos passados ​​como parâmetros de construtor ou se sua classe possui variáveis ​​const, você não tem outra opção, exceto o uso de listas de inicializadores.

Naveen
fonte
7
O mesmo vale para os membros const, eu acredito.
Richard Corden
Sim, não é possível usar a atribuição para modificar as variáveis ​​const, portanto, ele deve ser inicializado.
perfil completo de Hareen Laks
23
  1. Inicialização da classe base

Um motivo importante para o uso da lista de inicializadores de construtores que não é mencionado nas respostas aqui é a inicialização da classe base.

De acordo com a ordem de construção, a classe base deve ser construída antes da classe filho. Sem a lista de inicializadores de construtores, isso é possível se sua classe base tiver um construtor padrão, que será chamado imediatamente antes de inserir o construtor da classe filho.

Mas, se sua classe base tiver apenas construtor parametrizado, você deverá usar a lista de inicializadores de construtores para garantir que sua classe base seja inicializada antes da classe filho.

  1. Inicialização de subobjetos que possuem apenas construtores parametrizados

  2. Eficiência

Usando a lista de inicializadores de construtores, você inicializa seus membros de dados no estado exato que você precisa no seu código, em vez de inicializá-los no estado padrão e depois mudar o estado para o que você precisa no seu código.

  1. Inicializando membros de dados const não estáticos

Se membros de dados const não estáticos da sua classe tiverem construtores padrão e você não usar a lista de inicializadores de construtores, não poderá inicializá-los no estado pretendido, pois serão inicializados no estado padrão.

  1. Inicialização de membros de dados de referência

Os membros dos dados de referência devem ser inicializados quando o compilador entra no construtor, pois as referências não podem ser apenas declaradas e inicializadas posteriormente. Isso é possível apenas com a lista de inicializadores de construtores.

yuvi
fonte
10

Além dos problemas de desempenho, há outro muito importante que eu chamaria de manutenção e extensibilidade de código.

Se um T for POD e você começar a preferir a lista de inicialização, se uma vez T for alterado para um tipo não-POD, não será necessário alterar nada em torno da inicialização para evitar chamadas desnecessárias ao construtor, porque ele já está otimizado.

Se o tipo T tiver construtor padrão e um ou mais construtores definidos pelo usuário e uma vez que você decidir remover ou ocultar o padrão, se a lista de inicialização tiver sido usada, não será necessário atualizar o código se os construtores definidos pelo usuário eles já estão implementados corretamente.

O mesmo com membros const ou membros de referência, digamos que inicialmente T seja definido da seguinte maneira:

struct T
{
    T() { a = 5; }
private:
    int a;
};

Em seguida, você decide qualificar como const, se você usasse a lista de inicialização desde o início, essa era uma mudança de linha única, mas com o T definido como acima, também é necessário cavar a definição do construtor para remover a atribuição:

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

Não é segredo que a manutenção é muito mais fácil e menos propensa a erros se o código foi escrito não por um "macaco de código", mas por um engenheiro que toma decisões com base em considerações mais profundas sobre o que está fazendo.

mloskot
fonte
5

Antes de o corpo do construtor ser executado, todos os construtores para sua classe pai e, em seguida, para seus campos são chamados. Por padrão, os construtores sem argumento são chamados. As listas de inicialização permitem escolher qual construtor é chamado e quais argumentos esse construtor recebe.

Se você possui um campo de referência ou const, ou se uma das classes usadas não possui um construtor padrão, você deve usar uma lista de inicialização.

Jamal Zafar
fonte
2
// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate 
                     // constructors and operators
        variable = a;
    }
};

Aqui, o compilador segue as etapas a seguir para criar um objeto do tipo MyClass
1. O construtor do tipo é chamado primeiro para "a".
2. O operador de atribuição de “Tipo” é chamado dentro do corpo do construtor MyClass () para atribuir

variable = a;
  1. E, finalmente, o destruidor de "Tipo" é chamado de "a", pois fica fora do escopo.

    Agora considere o mesmo código com o construtor MyClass () com a lista de inicializadores

    // With Initializer List
     class MyClass {
    Type variable;
    public:
    MyClass(Type a):variable(a) {   // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
    }
    };

    Com a lista de inicializadores, as seguintes etapas são seguidas pelo compilador:

    1. O construtor de cópias da classe "Tipo" é chamado para inicializar: variável (a). Os argumentos na lista de inicializadores são usados ​​para copiar a construção "variável" diretamente.
    2. O destruidor de "Tipo" é chamado de "a", pois fica fora do escopo.
Rahul Singh
fonte
2
Embora esse trecho de código possa resolver a questão, incluir uma explicação fora do código realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código. Por favor, tente também não sobrecarregar seu código com comentários explicativos, isso reduz a legibilidade do código e das explicações! Meta.stackexchange.com/q/114762/308249
davejal 25/11
2
Por favor, escreva seu próprio entendimento ou compartilhe o link com a fonte original (aqui, geeksforgeeks.com) em vez de apenas copiar e colar.
Yuvi
1

Apenas para adicionar algumas informações adicionais para demonstrar quanta diferença a lista de inicialização de membros pode causar . Na consulta de soma de intervalo 303 leetcode - Imutável, https://leetcode.com/problems/range-sum-query-immutable/ , em que você precisa construir e inicializar para zerar um vetor com determinado tamanho. Aqui estão duas implementações e comparações de velocidade diferentes.

Sem a lista de inicialização dos membros , para obter AC, isso me custou cerca de 212 ms .

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
    preSum = vector<int>(nums.size()+1, 0);
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

Agora, usando a lista de inicialização de membros , o tempo para obter AC é de cerca de 108 ms . Com este exemplo simples, é óbvio que a lista de inicialização de membros é muito mais eficiente . Toda a medição é do tempo de execução do LC.

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) { 
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};
Yi Wang
fonte
0

Sintaxe:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

     Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
     {
           // Constructor body
     }
  };

Lista de necessidade de inicialização:

 class Sample
 {
     public:
         int Sam_x;
         int Sam_y;

     Sample()     */* Object and variables are created - i.e.:declaration of variables */*
     { // Constructor body starts 

         Sam_x = 1;      */* Defining a value to the variable */* 
         Sam_y = 2;

     } // Constructor body ends
  };

no programa acima, quando o construtor da classe é executado, Sam_x e Sam_y são criados. Em seguida, no corpo do construtor, essas variáveis ​​de dados do membro são definidas.

Casos de uso:

  1. Variáveis ​​Const e Reference em uma Classe

Em C, as variáveis devem ser definidas durante a criação. da mesma maneira em C ++, devemos inicializar a variável Const e Reference durante a criação do objeto usando a lista de Inicialização. se fizermos a inicialização após a criação do objeto (dentro do corpo do construtor), obteremos um erro de tempo de compilação.

  1. Objetos membros da classe Sample1 (base) que não possuem construtor padrão

     class Sample1 
     {
         int i;
         public:
         Sample1 (int temp)
         {
            i = temp;
         }
     };
    
      // Class Sample2 contains object of Sample1 
     class Sample2
     {
      Sample1  a;
      public:
      Sample2 (int x): a(x)      /* Initializer list must be used */
      {
    
      }
     };

Ao criar um objeto para a classe derivada, que internamente chama o construtor da classe derivada e chama o construtor da classe base (padrão). se a classe base não tiver construtor padrão, o usuário receberá um erro de tempo de compilação. Para evitar, devemos ter ou

 1. Default constructor of Sample1 class
 2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
  1. O nome do parâmetro do construtor da classe e o membro de dados de uma classe são os mesmos:

     class Sample3 {
        int i;         /* Member variable name : i */  
        public:
        Sample3 (int i)    /* Local variable name : i */ 
        {
            i = i;
            print(i);   /* Local variable: Prints the correct value which we passed in constructor */
        }
        int getI() const 
        { 
             print(i);    /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
             return i; 
        }
     };

Como todos sabemos, a variável local com prioridade mais alta do que a variável global se as duas variáveis ​​tiverem o mesmo nome. Nesse caso, o programa considera o valor "i" {variável do lado esquerdo e direito. ou seja: i = i} como variável local no construtor Sample3 () e variável de membro da classe (i) foi substituída. Para evitar, devemos usar

  1. Initialization list 
  2. this operator.
Eswaran Pandi
fonte