Pregunta

Los constructores deben inicializar todos sus objetos miembros a través de la lista de inicializador si es posible. Es más eficiente que construir los constructores a través de la asignación dentro del cuerpo del constructor.

¿Podría alguien explicar por qué es más eficiente usar la lista de inicializador con la ayuda de un ejemplo?

¿Fue útil?

Solución

Considere este programa:

#include <iostream>

struct A {
  A() { std::cout << "A::A()\n"; }
  A(int) { std::cout << "A::(int)\n"; }
  void operator=(const A&) { std::cout << "A::operator=(const A&)\n"; }
};

struct C1 {
  A a;
  C1(int i) { 
    a = i;
  }
};

struct C2 {
  A a;
  C2(int i)  : a(i) {}
};

int main() {
  std::cout << "How expesive is it to create a C1?\n";
  { C1 c1(7); }
  std::cout << "How expensive is it to create a C2?\n";
  { C2 c2(7); }
}

En mi sistema (Ubuntu 11.10, G ++ 4.6.1), el programa produce esta salida:

How expesive is it to create a C1?
A::A()
A::(int)
A::operator=(const A&)
How expensive is it to create a C2?
A::(int)

Ahora, considere por qué está haciendo eso. En el primer caso, C1::C1(int), a debe ser construido predeterminado antes C1El constructor se puede invocar. Entonces debe asignarse a vía operator=. En mi ejemplo trivial, no hay int Operador de asignación disponible, por lo que tenemos que construir un A fuera de un int. Por lo tanto, el costo de no usar un inicializador es: un constructor predeterminado, uno int constructor y un operador de asignación.

En el segundo caso, C2::C2(int), Solo el int Se invoca el constructor. Cualquiera que sea el costo de un incumplimiento A El constructor podría ser, claramente el costo de C2:C2(int) no es mayor que el costo de C1::C1(int).


O considere esta alternativa. Supongamos que agregamos el siguiente miembro a A:

void operator=(int) { std::cout << "A::operator=(int)\n"; }

Entonces la salida se leería:

How expesive is it to create a C1?
A::A()
A::operator=(int)
How expensive is it to create a C2?
A::(int)

Ahora es imposible decir en general qué forma es más eficiente. En su clase específica, ¿es el costo de un constructor predeterminado más el costo de una tarea más costoso que un constructor no predeterminado? Si es así, entonces la lista de inicialización es más eficiente. De lo contrario no lo es.

La mayoría de las clases que he escrito se inicializarían de manera más eficiente en una lista de inicio. Pero, esa es una regla de pulgar, y puede no ser cierto para todos los casos posibles.

Otros consejos

Bueno, de lo contrario llamas al constructor predeterminado y después realizar una tarea. Ese es un paso más y puede ser realmente ineficiente dependiendo de la naturaleza de la inicialización.

Porque se inicializa directamente, en lugar de inicializar predeterminados y luego asignar. Es probable que no importe el rendimiento para las cápsulas, pero lo hará si el constructor de tipos está haciendo un trabajo de peso pesado.

Además, en algunos casos tú deber Use la lista Init, por lo que siempre debe hacerlo por consistencia.

Desde el Preguntas frecuentes de C ++ :

Considere el siguiente constructor que inicializa el objeto miembro X_ usando una lista de inicialización: Fred :: Fred (): x_ (lo que sea) {}. El beneficio más común de hacer esto es el rendimiento mejorado. Por ejemplo, si la expresión de lo que sea del mismo tipo que la variable miembro x_, el resultado de la expresión que se construya directamente dentro de x_, el compilador no hace una copia separada del objeto. Incluso si los tipos no son los mismos, el compilador generalmente puede hacer un mejor trabajo con las listas de inicialización que con las tareas.

La otra forma (ineficiente) de construir constructores es mediante asignación, como: fred :: fred () {x_ = lo que sea; }. En este caso, la expresión lo que cause un objeto temporal separado que se cree, y este objeto temporal se pasa al operador de asignación del objeto X_. Entonces ese objeto temporal se destruye en el;. Eso es ineficiente.

Como si eso no fuera lo suficientemente malo, hay otra fuente de ineficiencia cuando se usa la asignación en un constructor: el objeto miembro se construirá completamente por su constructor predeterminado, y esto podría, por ejemplo, asignar una cantidad predeterminada de memoria o abrir algunos valores predeterminados expediente. Todo este trabajo podría ser para nada si el operador de expresión y/o asignación hace que el objeto cierre ese archivo y/o libere esa memoria (por ejemplo, si el constructor predeterminado no asignó un grupo de memoria lo suficientemente grande o si se abre el archivo incorrecto).

Para evitar la doble inicialización.

class B
{
//whatever
};

class A
{
   B _b;
public:
   A(B& b)
};

Ahora los dos casos:

//only initializes _b once via a copy constructor
A::A(B& b) : _b(b)
{
}

//initializes _b once before the constructor body, and then copies the new value
A::A(B& b)
{
   //_b is already initialized here
   //....
   //one extra instruction:
   _b = b;
}

En lo que respecta a los tipos de POD, la inicialización y la asignación deben ser equivalentes, ya que se dejan no inicializados si no se realiza una inicialización explícitamente, por lo que la única operación sigue siendo la asignación.

Las cosas son diferentes para las clases que tienen constructores predeterminados y operadores de asignación: en lugar de crear el objeto directamente en el estado correcto, primero se debe llamar al constructor predeterminado, y luego el operador de asignación (dentro del cuerpo del constructor). Esto seguramente es más ineficiente que inicializar el objeto con el constructor correcto directamente desde el principio (dos pasos en lugar de uno, y el primer paso, la construcción predeterminada, generalmente se desperdicia por completo).

La inicialización directa también produce la ventaja de que puede estar seguro de que, si la ejecución alcanzó el cuerpo del constructor, todos los diversos campos ya están en su estado "correcto".

Supongamos que tiene un miembro de datos en su clase que es el tipo de std :: string. Cuando se ejecuta el constructor de esta clase, la clase de cadena de constructor predeterminada se llamaría automáticamente porque los objetos se inicializan antes del cuerpo del constructor.

Si está asignando la cadena dentro del cuerpo del constructor, entonces se crearía un objeto temporal y se le dará al operador de asignación de la cadena. El objeto temporal será destruido al final de la declaración de asignación. Si hace esto en la lista de inicializador, no se creará un objeto temporal.

class Person
{
public:
    Person(string s);
    ~Person();
private:
    string name;
};


// case 1
Person::Person(string s)
{
   name = s;   // creates a temporary object
}

// case 2
Person::Person(string s):name(s) {} // whereas this will not create a temporary object.

Porque si está utilizando la lista Inizializer, lo que está llamando es la copia del constructor de ese objeto.

Mientras que si inicializa objetos dentro del cuerpo del constructor, está haciendo una tarea.

Ejemplo: Aquí estoy llamando al constructor de copias de int.


  myClass::myClass( int x ) : member(x) {}

mientras aquí estoy llamando al operador = (const int &). la asignación


    myClass::myClass( int x )
    {
         member = x;
    }

Por lo general, la asignación hace más operación que una copia simple. ¡Debe mantener en cuenta también objeto temporal!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top