En este caso específico, ¿hay alguna diferencia entre usar una lista de inicializadores de miembros y asignar valores en un constructor?

90

Internamente y sobre el código generado, ¿existe realmente una diferencia entre:

MyClass::MyClass(): _capacity(15), _data(NULL), _len(0)
{
}

y

MyClass::MyClass()
{
  _capacity=15;
  _data=NULL;
  _len=0
}

Gracias...

Stef
fuente

Respuestas:

60

Suponiendo que esos valores son tipos primitivos, entonces no, no hay diferencia. Las listas de inicialización solo hacen una diferencia cuando tiene objetos como miembros, ya que en lugar de usar la inicialización predeterminada seguida de la asignación, la lista de inicialización le permite inicializar el objeto a su valor final. De hecho, esto puede ser notablemente más rápido.

templatetypedef
fuente
17
como dijo Richard, hay una diferencia si esos valores son tipos primitivos y const, las listas de inicialización son la única forma de asignar valores a los miembros const.
thbusch
11
Solo funciona como se describe cuando las variables no son referencias o constantes, si son constantes o de referencia, ni siquiera se compilará sin usar la lista de inicialización.
stefanB
77

Necesita usar la lista de inicialización para inicializar miembros constantes, referencias y clase base

Cuando necesite inicializar miembros constantes, referencias y pasar parámetros a constructores de clases base, como se menciona en los comentarios, debe usar la lista de inicialización.

struct aa
{
    int i;
    const int ci;       // constant member

    aa() : i(0) {} // will fail, constant member not initialized
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0) { ci = 3;} // will fail, ci is constant
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0), ci(3) {} // works
};

La clase / estructura de ejemplo (no exhaustiva) contiene una referencia:

struct bb {};

struct aa
{
    bb& rb;
    aa(bb& b ) : rb(b) {}
};

// usage:

bb b;
aa a(b);

Y ejemplo de inicialización de la clase base que requiere un parámetro (por ejemplo, sin constructor predeterminado):

struct bb {};

struct dd
{
    char c;
    dd(char x) : c(x) {}
};

struct aa : dd
{
    bb& rb;
    aa(bb& b ) : dd('a'), rb(b) {}
};
StefanB
fuente
4
¿Y si _capacity, _datay _lentienen tipos de clase sin constructores predeterminados accesibles?
CB Bailey
2
Usted llama a los constructores que están disponibles, si necesita establecer más valores, los llama desde el cuerpo de su constructor. La diferencia aquí es que no puede inicializar el constmiembro en el cuerpo del constructor, debe usar la lista de inicialización; los no constmiembros pueden inicializarse en la lista de inicialización o en el cuerpo del constructor.
stefanB
@stefan: No puedes llamar a constructores. Una clase sin constructor predeterminado debe inicializarse en la lista de inicializadores, al igual que los miembros const.
GManNickG
2
también debe inicializar las referencias en la lista de inicialización
Andriy Tylychko
2
@stefanB: Mis disculpas si he insinuado que no entiendes esto cuando en realidad lo haces. En su respuesta, solo ha indicado cuándo se debe nombrar una base o miembro en la lista de inicializadores, no ha explicado cuál es la diferencia conceptual entre inicializar en la lista de inicializadores y asignar en el cuerpo del constructor, cuál es mi malentendido puede haber venido de.
CB Bailey
18

Si. En el primer caso puedes declarar _capacity, _datay _lencomo constantes:

class MyClass
{
private:
    const int _capacity;
    const void *_data;
    const int _len;
// ...
};

Esto sería importante si desea garantizar la constidoneidad de estas variables de instancia al calcular sus valores en tiempo de ejecución, por ejemplo:

MyClass::MyClass() :
    _capacity(someMethod()),
    _data(someOtherMethod()),
    _len(yetAnotherMethod())
{
}

constlas instancias deben inicializarse en la lista de inicializadores o los tipos subyacentes deben proporcionar constructores públicos sin parámetros (lo que hacen los tipos primitivos).

Richard Cook
fuente
2
Lo mismo ocurre con las referencias. Si su clase tiene miembros de referencia, deben inicializarse en la lista de inicializadores.
Mark Ransom
7

Creo que este enlace http://www.cplusplus.com/forum/articles/17820/ ofrece una excelente explicación, especialmente para aquellos que son nuevos en C ++.

La razón por la que las listas de inicializadores son más eficientes es que dentro del cuerpo del constructor, solo tienen lugar las asignaciones, no la inicialización. Entonces, si está tratando con un tipo no incorporado, el constructor predeterminado para ese objeto ya ha sido llamado antes de ingresar el cuerpo del constructor. Dentro del cuerpo del constructor, estás asignando un valor a ese objeto.

En efecto, esta es una llamada al constructor predeterminado seguida de una llamada al operador de asignación de copia. La lista de inicializadores le permite llamar directamente al constructor de copia, y esto a veces puede ser significativamente más rápido (recuerde que la lista de inicializadores está antes del cuerpo del constructor)

user929404
fuente
5

Agregaré que si tiene miembros de tipo de clase sin un constructor predeterminado disponible, la inicialización es la única forma de construir su clase.

Fred Larson
fuente
3

Una gran diferencia es que la asignación puede inicializar miembros de una clase principal; el inicializador solo funciona en miembros declarados en el ámbito de clase actual.

Mark Ransom
fuente
2

Depende de los tipos involucrados. La diferencia es similar entre

std::string a;
a = "hai";

y

std::string a("hai");

donde la segunda forma es la lista de inicialización, es decir, hace una diferencia si el tipo requiere argumentos de constructor o es más eficiente con argumentos de constructor.

Perrito
fuente
1

La verdadera diferencia se reduce a cómo el compilador gcc genera código de máquina y coloca la memoria. Explique:

  • (fase1) Antes del cuerpo de inicio (incluida la lista de inicio): el compilador asigna la memoria necesaria para la clase. ¡La clase ya está viva!
  • (fase2) En el cuerpo de inicio: dado que la memoria está asignada, cada asignación ahora indica una operación en la variable ya existente / 'inicializada'.

Ciertamente, hay otras formas de manejar miembros de tipo constante. Pero para facilitarles la vida, los escritores del compilador de gcc deciden establecer algunas reglas

  1. Los miembros de tipo const deben inicializarse antes que el cuerpo init.
  2. Después de la fase 1, cualquier operación de escritura solo es válida para miembros no constantes.
lukmac
fuente
1

Solo hay una forma de inicializar instancias de clase base y variables miembro no estáticas y es usar la lista de inicializadores.

Si no especifica una variable de miembro base o no estática en la lista de inicializadores de su constructor, entonces ese miembro o base se inicializará por defecto (si el miembro / base es un tipo de clase que no es POD o una matriz de clase que no es POD tipos) o dejar sin inicializar de otra manera.

Una vez que se ingresa el cuerpo del constructor, todas las bases o miembros se habrán inicializado o dejado sin inicializar (es decir, tendrán un valor indeterminado). No hay ninguna oportunidad en el cuerpo del constructor para influir en cómo se deben inicializar.

Es posible que pueda asignar nuevos valores a miembros en el cuerpo del constructor, pero no es posible asignar a constmiembros o miembros de tipo de clase que se han hecho no asignables y no es posible volver a vincular referencias.

Para tipos incorporados y algunos tipos definidos por el usuario, la asignación en el cuerpo del constructor puede tener exactamente el mismo efecto que inicializar con el mismo valor en la lista de inicializadores.

Si no nombra un miembro o una base en una lista de inicializadores y esa entidad es una referencia, tiene un tipo de clase sin un constructor predeterminado declarado por el usuario accesible, está constcalificado y tiene un tipo POD o es un tipo de clase POD o matriz de tipo de clase POD que contiene un constmiembro calificado (directa o indirectamente), el programa está mal formado.

CB Bailey
fuente
0

Si escribe una lista de inicializadores, lo hace todo en un solo paso; si no escribe una lista de iniciadores, realizará 2 pasos: uno para la declaración y otro para asignar el valor.

L. Vicente Mangas
fuente
0

Existe una diferencia entre la lista de inicialización y la declaración de inicialización en un constructor. Consideremos el siguiente código:

#include <initializer_list>
#include <iostream>
#include <algorithm>
#include <numeric>

class MyBase {
public:
    MyBase() {
        std::cout << __FUNCTION__ << std::endl;
    }
};

class MyClass : public MyBase {
public:
    MyClass::MyClass() : _capacity( 15 ), _data( NULL ), _len( 0 ) {
        std::cout << __FUNCTION__ << std::endl;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

class MyClass2 : public MyBase {
public:
    MyClass2::MyClass2() {
        std::cout << __FUNCTION__ << std::endl;
        _capacity = 15;
        _data = NULL;
        _len = 0;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

int main() {
    MyClass c;
    MyClass2 d;

    return 0;
}

Cuando se usa MyClass, todos los miembros se inicializarán antes de que se ejecute la primera instrucción en un constructor.

Pero, cuando se usa MyClass2, todos los miembros no se inicializan cuando se ejecuta la primera instrucción en un constructor.

En un caso posterior, puede haber un problema de regresión cuando alguien agregó código en un constructor antes de que se inicialice un miembro determinado.

user928143
fuente
0

Aquí hay un punto que no vi a otros referirse a él:

class temp{
public:
   temp(int var);
};

La clase temporal no tiene un ctor predeterminado. Cuando lo usamos en otra clase de la siguiente manera:

class mainClass{
public:
 mainClass(){}
private:
  int a;
  temp obj;
};

el código no se compilará, porque el compilador no sabe cómo inicializar obj, porque solo tiene un ctor explícito que recibe un valor int, por lo que tenemos que cambiar el ctor de la siguiente manera:

mainClass(int sth):obj(sth){}

Por lo tanto, no se trata solo de constantes y referencias.

hosh0425
fuente