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

Me gusta usar listas de inicialización de miembros con mis constructores … pero hace tiempo que olvidé las razones detrás de esto …

¿Utiliza listas de inicialización de miembros en sus constructores? Si es así, ¿por qué? ¿Si no, porque no?

Para los miembros de la clase POD , no hace ninguna diferencia, es solo una cuestión de estilo. Para los miembros de la clase que son clases, entonces 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() { ax = 3; } private: A a; }; 

En este caso, el constructor para B llamará al constructor predeterminado para A , y luego inicializará ax a 3. Una mejor manera sería que el constructor de B llame directamente al constructor de A en la lista de inicializadores:

 B() : a(3) { } 

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

Además, si una clase no tiene un constructor predeterminado, o si tiene una variable de miembro const , debe usar una 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; }; 

Además de los motivos de rendimiento mencionados anteriormente, si su clase almacena referencias a objetos pasados ​​como parámetros de construcción o su clase tiene variables const, entonces no tiene otra opción que utilizar listas de inicializadores.

  1. Inicialización de la clase base

Una razón importante para usar la lista de inicializadores de constructores 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 infantil. Sin lista de inicializadores de constructor, esto es posible si su clase base tiene un constructor predeterminado que se llamará justo antes de ingresar al constructor de la clase secundaria.

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

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

  2. Eficiencia

Al usar la lista de inicializadores de constructor, inicializa los miembros de su información al estado exacto que necesita en su código en lugar de inicializarlos a su estado predeterminado y luego cambiar su estado al que necesita en su código.

  1. Inicializando miembros de datos de const no estáticos

Si los miembros de la información no estática const en su clase tienen constructores por defecto y no utiliza la lista de inicializadores del constructor, no podrá inicializarlos en el estado deseado, ya que se inicializarán en su estado predeterminado.

  1. Inicialización de miembros de datos de referencia

Los miembros de datos de referencia se deben inicializar cuando el comstackdor ingrese al constructor ya que las referencias no se pueden declarar e inicializar más tarde. Esto solo es posible con la lista de inicializadores del constructor.

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

Si una T es POD y usted comienza a preferir la lista de inicialización, entonces 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 ellos ya están implementados correctamente.

Lo mismo con los miembros de const o los 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 const, si usaría la lista de inicialización desde el principio, entonces esto era un cambio de línea única, pero teniendo la T definida como arriba, también requiere excavar 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 no fue escrito por un “mono de código” sino por un ingeniero que toma decisiones basándose en una consideración más profunda de lo que está haciendo.

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

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

 // 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 comstackdor sigue los siguientes pasos para crear un objeto de tipo MyClass
1. El constructor de tipo se llama primero para “a”.
2. El operador de asignación de “Tipo” se llama dentro del cuerpo del constructor de MyClass () para asignar

 variable = a; 
  1. Y luego, finalmente, se llama al destructor de “Tipo” por “a”, ya que está fuera del scope.

    Ahora considere el mismo código con el constructor MyClass () con Initializer List

     // 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 comstackdor sigue los siguientes pasos:

    1. Copiar el constructor de la clase “Tipo” se llama para inicializar: variable (a). Los argumentos en la lista de inicializadores se utilizan para copiar constructo “variable” directamente.
    2. Destructor de “Tipo” se llama para “a” ya que sale del scope.

Sintaxis:

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

Necesidad de lista de inicialización:

  class Sample { public: int Sam_x; int Sam_y; Sample() */* Object and variables are created - ie:declaration of variables */* { // Constructor body starts Sam_x = 1; */* Defining a value to the variable */* Sam_y = 2; } // Constructor body ends }; 

en el progtwig anterior, cuando se ejecuta el constructor de la clase, se crean Sam_x y Sam_y . Luego, en el cuerpo del constructor, se definen las variables de datos de los 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 del objeto usando la lista de Inicialización. si hacemos la inicialización después de la creación del objeto (cuerpo constructor interno), obtendremos un error de tiempo de comstackción.

  1. Objetos miembros de la clase Sample1 (base) que no tienen 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 llamará internamente al constructor de clase derivado y al constructor de clase base (predeterminado). si la clase base no tiene un constructor predeterminado, el usuario obtendrá un error de tiempo de comstackción. Para evitarlo, debemos tener cualquiera

  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 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 prioridad más alta que la variable global si ambas variables tienen el mismo nombre. En este caso, el progtwig considera el valor “i” {variable izquierda y derecha. es decir: i = i} como variable local en el constructor Sample3 () y la variable miembro de la clase (i) se superó. Para evitarlo, debemos usar cualquiera

  1. Initialization list 2. this operator. 

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

Sin la lista de inicialización de miembros , para obtener AC me costó unos 212 ms .

 class NumArray { public: vector preSum; NumArray(vector nums) { preSum = vector(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, al usar la lista de inicialización de miembros , el tiempo para obtener CA 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 medida es desde el tiempo de ejecución desde LC.

 class NumArray { public: vector preSum; NumArray(vector 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]; } };