Saltar a contenido

Última actualización:2026-10-12

C++ Idioma de la lista de inicialización de clases

  • Los miembros const solo pueden inicializarse en la lista de inicialización del constructor
  • Los miembros de tipo referencia también solo pueden inicializarse en la lista de inicialización del constructor
  • Los miembros objeto (cuando la clase correspondiente al objeto miembro no tiene constructor por defecto) también solo pueden inicializarse en la lista de inicialización del constructor

Anidamiento entre clases

Primera forma: usar la lista de inicialización.

class Animal {
public:
    Animal() {
        std::cout << "Animal() is called" << std::endl;
    }

    Animal(const Animal &) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal &) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog {
public:
    Dog(const Animal &animal) : __animal(animal) {
        std::cout << "Dog(const Animal &animal) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

private:
    Animal __animal;
};

int main() {
    Animal animal;
    std::cout << std::endl;
    Dog d(animal);
    std::cout << std::endl;
    return 0;
}

Resultado de la ejecución:

Animal() is called

Animal (const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called

Analizando de arriba abajo, en orden:

En la función main, Animal animal; invoca al constructor por defecto.

Dog d(animal); equivale a:

Animal __animal = animal;
En realidad se llama al constructor de copia, por lo que se imprime:
Animal (const Animal &) is called
Después se imprime la salida dentro del constructor de Dog.

Finalmente se invoca al destructor y termina el programa.

Segunda forma: inicializar el objeto mediante asignación en el constructor.

El constructor se modifica así:

Dog(const Animal &animal) {
    __animal = animal;
    std::cout << "Dog(const Animal &animal) is called" << std::endl;
}
En este caso la salida es:
Animal() is called

Animal() is called
Animal & operator=(const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called

De ahí se concluye:

Al llamar a Dog d(animal);, equivale a:

Definir primero el objeto y asignar después; por ello se invoca primero al constructor por defecto y después a la sobrecarga del operador =.

// 假设之前已经有了animal对象
Animal __animal;
__animal = animal;

Resumen

De lo anterior obtenemos las siguientes conclusiones:

  • Si una clase contiene otras class o struct definidas por nosotros, usar la lista de inicialización equivale en realidad a crear el objeto e inicializarlo a la vez
  • En cambio, con la asignación dentro de la clase equivale a definir primero el objeto y asignar después; normalmente se invoca primero al constructor por defecto y luego a la sobrecarga del operador =.

En una relación de herencia sin constructor por defecto

Consideremos ahora cambiar la relación anterior por herencia, y modifiquemos los constructores de Animal y Dog con el siguiente código:

class Animal {
public:
    Animal(int age) {
        std::cout << "Animal(int age) is called" << std::endl;
    }

    Animal(const Animal & animal) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal & amimal) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog : Animal {
public:
    Dog(int age) : Animal(age) {
        std::cout << "Dog(int age) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

};

Lo anterior pasa los parámetros al constructor parametrizado de la clase base mediante la lista de inicialización; ¿qué efectos indeseables aparecen si no se pasan por la lista de inicialización?

Quitamos la lista de inicialización

Dog(int age)  {
    std::cout << "Dog(int age) is called" << std::endl;
}
Ejecutamos el programa:

error: no matching function for call to ‘Animal::Animal()’

Como Animal no tiene constructor por defecto, se produce el error; este tipo de problema es catastrófico y debemos evitarlo a toda costa, pasando los parámetros por la lista de inicialización para construir la clase base.

Miembros de datos const y de tipo referencia dentro de una clase

En particular los miembros de tipo referencia, ¡deben inicializarse con la lista de inicialización y no mediante asignación!

Por ejemplo: añadimos miembros privados al Animal anterior y modificamos el constructor:

class Animal {
public:
    Animal(int age,std::string name) {
        std::cout << "Animal(int age) is called" << std::endl;
    }
private:
    int &age_;
    const std::string name_;
};

Se produce el siguiente error:

error: uninitialized reference member in ‘int&’

Debería cambiarse por lo siguiente:

Animal(int age, std::string name) : age_(age), name_(name) {
    std::cout << "Animal(int age) is called" << std::endl;
}

Temas relacionados

Comentarios