¿Por qué debería preferir usar la lista de inicialización de miembros?

Respuestas:

278

Para los miembros de la clase POD , no hay diferencia, es solo una cuestión de estilo. Para los miembros de la clase que son clases, evita una llamada innecesaria a un constructor predeterminado. Considerar:

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

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

En este caso, el constructor para Bllamará al constructor predeterminado para A, y luego se inicializará a.xa 3. Una mejor forma sería que Bel constructor de A' llame directamente al constructor en la lista de inicializadores:

B()
  : a(3)
{
}

Esto solo llamaría Aal A(int)constructor y no al constructor predeterminado. En este ejemplo, la diferencia es insignificante, pero imagine si lo desea, Ael constructor predeterminado hizo más, como asignar memoria o abrir archivos. No querrías hacer eso innecesariamente.

Además, si una clase no tiene un constructor predeterminado, o si tiene una constvariable miembro, debe usar una lista de inicializador:

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
fuente
55
una
visita
55
¿Por qué no usar "a (3);" o "a = A (3);" en el cuerpo del constructor predeterminado de B?
Sergey
1
¿Podría explicar a qué se refiere con POD?
Jonas Stein
2
@JonasStein POD es un conjunto bien definido de reglas que pertenecen a estructuras de datos simples (en lugar de clases completas). Lea las preguntas frecuentes para obtener más información: stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506
2
@Sergey, el constructor predeterminado de A todavía se llamaría.
Vassilis
44

Además de las razones de rendimiento mencionadas anteriormente, si su clase almacena referencias a objetos pasados ​​como parámetros de constructor o su clase tiene variables constantes, entonces no tiene otra opción que usar listas de inicializador.

Naveen
fuente
77
Lo mismo ocurre con los miembros const, creo.
Richard Corden
Sí, no se puede utilizar la asignación para modificar las variables constantes, por lo que debe inicializarse.
Hareen Laks
23
  1. Inicialización de la clase base.

Una razón importante para usar la lista de inicializador de constructor que no se menciona en las respuestas aquí es la inicialización de la clase base.

Según el orden de construcción, la clase base debe construirse antes de la clase secundaria. Sin la lista de inicializador de constructor, esto es posible si su clase base tiene un constructor predeterminado que se llamará justo antes de ingresar el constructor de la clase secundaria.

Pero, si su clase base solo tiene un constructor parametrizado, entonces debe usar la lista de inicializador del constructor para asegurarse de que su clase base se inicialice antes que la clase secundaria.

  1. Inicialización de subobjetos que solo tienen constructores parametrizados

  2. Eficiencia

Usando la lista de inicializador de constructor, inicializa sus miembros de datos al estado exacto que necesita en su código en lugar de inicializarlos primero a su estado predeterminado y luego cambiar su estado al que necesita en su código.

  1. Inicializando miembros de datos constantes no estáticos

Si los miembros de datos constantes no estáticos de su clase tienen constructores predeterminados y no utiliza la lista de inicializadores de constructores, no podrá inicializarlos al estado deseado, ya que se inicializarán a su estado predeterminado.

  1. Inicialización de miembros de datos de referencia.

Los miembros de datos de referencia deben inicializarse cuando el compilador ingresa al constructor, ya que las referencias no pueden declararse e inicializarse más tarde. Esto solo es posible con la lista de inicializador de constructor.

Yuvi
fuente
10

Además de los problemas de rendimiento, hay otro muy importante que llamaría mantenibilidad y extensibilidad del código.

Si un T es POD y comienza a preferir la lista de inicialización, si una vez T cambiará a un tipo que no sea POD, no necesitará cambiar nada en torno a la inicialización para evitar llamadas innecesarias al constructor porque ya está optimizado.

Si el tipo T tiene un constructor predeterminado y uno o más constructores definidos por el usuario y una vez que decide eliminar u ocultar el predeterminado, entonces si se utilizó la lista de inicialización, no necesita actualizar el código si sus constructores definidos por el usuario porque ya están implementados correctamente.

Lo mismo con los miembros const o miembros de referencia, digamos que inicialmente T se define de la siguiente manera:

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

Luego, decide calificar a como constante, si usaría la lista de inicialización desde el principio, entonces este fue un cambio de línea simple, pero tener la T definida como anteriormente, también requiere cavar la definición del constructor para eliminar la asignación:

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

No es un secreto que el mantenimiento es mucho más fácil y menos propenso a errores si el código fue escrito no por un "código mono" sino por un ingeniero que toma decisiones basadas en una consideración más profunda sobre lo que está haciendo.

mloskot
fuente
5

Antes de ejecutar el cuerpo del constructor, se invocan todos los constructores para su clase padre y luego para sus campos. Por defecto, se invocan los constructores sin argumentos. Las listas de inicialización le permiten elegir qué constructor se llama y qué argumentos recibe ese constructor.

Si tiene una referencia o un campo constante, o si una de las clases utilizadas no tiene un constructor predeterminado, debe usar una lista de inicialización.

Jamal Zafar
fuente
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;
    }
};

Aquí el compilador sigue los siguientes pasos para crear un objeto de tipo MyClass
1. El constructor de tipos se llama primero para "a".
2. El operador de asignación de "Tipo" se llama dentro del cuerpo del constructor MyClass () para asignar

variable = a;
  1. Y, finalmente, el destructor de "Tipo" se llama "a", ya que está fuera de alcance.

    Ahora considere el mismo código con el constructor MyClass () con la 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
    }
    };

    Con la Lista de inicializadores, el compilador sigue los siguientes pasos:

    1. El constructor de copia de la clase "Tipo" se llama para inicializar: variable (a). Los argumentos en la lista de inicializadores se utilizan para copiar la construcción "variable" directamente.
    2. Destructor de "Tipo" se llama "a" ya que está fuera de alcance.
Rahul Singh
fuente
2
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación fuera del código realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código. ¡Intente también no saturar su código con comentarios explicativos, esto reduce la legibilidad tanto del código como de las explicaciones! meta.stackexchange.com/q/114762/308249
davejal
2
Por favor, escriba su propio entendimiento o simplemente comparta el enlace a la fuente original (aquí, geeksforgeeks.com) en lugar de simplemente copiarlo y pegarlo.
yuvi
1

Solo para agregar información adicional para demostrar cuánta diferencia puede marcar la lista de inicialización de miembros . En el Leetcode 303 Range Sum Query - Immutable, https://leetcode.com/problems/range-sum-query-immutable/ , donde necesita construir e inicializar a cero un vector con cierto tamaño. Aquí hay dos diferentes implementaciones y comparaciones de velocidad.

Sin la lista de inicialización de miembros , obtener AC me costó unos 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];
}
};

Ahora, usando la lista de inicialización de miembros , el tiempo para obtener AC es de aproximadamente 108 ms . Con este simple ejemplo, es bastante obvio que la lista de inicialización de miembros es mucho más eficiente . Toda la medición es del tiempo de ejecución de 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
fuente
0

Sintaxis:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

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

Lista de necesidades de inicialización:

 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
  };

en el programa anterior, cuando se ejecuta el constructor de la clase, Sam_x y Sam_y . Luego, en el cuerpo del constructor, se definen las variables de datos de miembros.

Casos de uso:

  1. Const y variables de referencia en una clase

En C, las variables deben definirse durante la creación. de la misma manera en C ++, debemos inicializar la variable Const y Reference durante la creación de objetos mediante la lista de Inicialización. Si hacemos la inicialización después de la creación del objeto (dentro del cuerpo del constructor), obtendremos un error de tiempo de compilación.

  1. Objetos miembros de la clase Sample1 (base) que no tienen un constructor predeterminado

     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 */
      {
    
      }
     };

Al crear un objeto para la clase derivada que internamente llama al constructor de la clase derivada y llama al constructor de la clase base (predeterminado). Si la clase base no tiene un constructor predeterminado, el usuario obtendrá un error de tiempo de compilación. Para evitar, debemos tener

 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. El nombre del parámetro del constructor de la clase y el miembro de datos de una clase son los mismos:

     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, la variable local tiene la más alta prioridad que la variable global si ambas variables tienen el mismo nombre. En este caso, el programa considera el valor "i" {variable del lado izquierdo y derecho. es decir: i = i} como variable local en el constructor Sample3 () y la variable miembro de clase (i) se anuló. Para evitar, debemos usar cualquiera

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