Compartir a través de


Constructores (C++)

Un constructor es una función miembro especial que inicializa una instancia de su clase. Para llamar a un constructor, utilice el nombre de clase junto con los parámetros dentro de llaves o paréntesis.

class Box {
public:
    Box(int width, int length, int height){
        m_width = width;
        m_length = length;
        m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;

};
class SomeClass{
public:
    static void set_box(const Box& aBox) {}
};
int main(){

    Box box1(1, 2, 3);
    Box box2{ 2, 3, 4 };
    SomeClass::set_box(Box{ 5, 6, 7 });
}

Para obtener más información, vea Reglas para la declaración de constructores. Para obtener más información sobre la inicialización, vea Inicialización.

Orden de construcción

Un constructor realiza su trabajo en este orden:

  1. Llama a los constructores miembros y de clase base en el orden en que se declararon.

  2. Si la clase se deriva de clases base virtuales, inicializa los punteros base virtuales del objeto.

  3. Si la clase tiene o hereda funciones virtuales, inicializa los punteros de funciones virtuales del objeto. Los punteros de funciones virtuales apuntan a la tabla de funciones virtuales de la clase para permitir el enlace correcto de las llamadas de funciones virtuales al código.

  4. Ejecuta cualquier código en el cuerpo de su función.

En el ejemplo siguiente se muestra el orden en el que los constructores miembros y de clase base se llaman en el constructor para una clase derivada. Primero, se llama al constructor base, después los miembros de la clase base se inicializan en el orden en que aparecen en la declaración de clase y después se llama al constructor derivado.

#include <iostream>
using namespace std;

class Contained1 {
public:
    Contained1() {
        cout << "Contained1 constructor." << endl;
    }
};

class Contained2 {
public:
    Contained2() {
        cout << "Contained2 constructor." << endl;
    }
};

class Contained3 {
public:
    Contained3() {
        cout << "Contained3 constructor." << endl;
    }
};

class BaseContainer {
public:
    BaseContainer() {
        cout << "BaseContainer constructor." << endl;
    }
private:
    Contained1 c1;
    Contained2 c2;
};

class DerivedContainer : public BaseContainer {
public:
    DerivedContainer() : BaseContainer() {
        cout << "DerivedContainer constructor." << endl;
    }
private:
    Contained3 c3;
};

int main() {
    DerivedContainer dc;
    int x = 3;
}

Este es el resultado:

Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.

Si un constructor inicia una excepción, el orden de destrucción es el inverso al orden de la construcción:

  1. El código del cuerpo de la función de constructor se desenredará.

  2. Los objetos miembro y de la clase base se destruirán en el orden inverso de la declaración.

  3. Si el constructor no delega, se destruirán todos los miembros y objetos de clase base totalmente implementados. Sin embargo, dado que el objeto en sí no está totalmente implementado, el destructor no se ejecutará.

Constructores explícitos

Al usar la palabra clave explicit en un constructor, puede evitar conversiones de tipo implícitas si el constructor solo tiene un parámetro o si todos los parámetros excepto uno tienen un valor predeterminado. Para obtener más información, vea Constructores (C++).

Constructores predeterminados

Los constructores predeterminados (es decir, los constructores que no tienen parámetros) siguen reglas ligeramente distintas.

Si no se declara ningún constructor en una clase, el compilador proporciona un constructor predeterminado:

class Box {
    int m_width;
    int m_length;
    int m_height;
};

int main(){

    Box box1{};
    Box box2;
}

Cuando llama a un constructor predeterminado e intenta utilizar paréntesis, se devuelve una advertencia:

class myclass{};
int main(){
myclass mc();     // warning C4930: prototyped function not called (was a variable definition intended?)
}

Este es un ejemplo del problema de Most Vexing Parse. Dado que la expresión del ejemplo se puede interpretar como la declaración de una función o como la invocación de un constructor predeterminado, y dado que los analizadores de C++ prefieren las declaraciones sobre otras cosas, la expresión se trata como una declaración de función. Para obtener más información, vea Most Vexing Parse.

Si se declaran constructores no predeterminados, el compilador no proporcionará un constructor predeterminado:

class Box {
public:
    Box(int width, int length, int height){
       m_width = width;
       m_length = length;
       m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;

};

int main(){

    Box box1(1, 2, 3);
    Box box2{ 2, 3, 4 };
    Box box4;     // compiler error C2512: no appropriate default constructor available
}

Si una clase no tiene ningún constructor predeterminado, una matriz de objetos de esa clase no se puede crear únicamente mediante una sintaxis de corchetes. Por ejemplo, dado el bloque de código anterior, no se puede declarar una matriz de marcos como esta:

Box boxes[3];    // compiler error C2512: no appropriate default constructor available

Sin embargo, puede utilizar un conjunto de listas de inicializadores para inicializar una matriz de marcos:

Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };

Constructores de copias y movimiento

Un constructor de copias utiliza una referencia a un objeto para hacer una copia de este. Si no se proporciona un constructor de copias para una clase, el compilador creará uno predeterminado, incluso aunque se haya declarado una operación de copia, de movimiento o un destructor. Para obtener más información, vea Reglas para la declaración de constructores.

Un constructor de movimiento permite la transferencia de memoria asignada desde un objeto a otro. Para obtener más información, vea Cómo: Escribir un constructor Move.

Constructores explícitamente establecidos como predeterminados y eliminados

Puede establecer explícitamente como predeterminados constructores de copia, constructores predeterminados, operadores de asignación de copia y destructores, pero no se permite establecer explícitamente como predeterminados constructores de movimiento y operadores de asignación de movimiento. (Esta compatibilidad está presente en Visual Studio de 2015.) Puede eliminar explícitamente todas las funciones especiales. Para obtener más información, vea Funciones establecidas como valor predeterminado y eliminadas explícitamente.

Constructores de clases derivadas

Un constructor de clase derivada siempre llama a un constructor de clase base, por lo que se pueden usar clases base completamente construidas antes de realizar cualquier trabajo adicional. Los constructores de clase base se llaman en orden de derivación: por ejemplo, si ClassA se deriva de ClassB, que se deriva de ClassC, se llama primero al constructor de ClassC, después al constructor de ClassB y, por último, al constructor de ClassA.

Si una clase base no tiene un constructor predeterminado, debe proporcionar los parámetros de constructor de clase base en el constructor de clase derivada:

class Box {
public:
    Box(int width, int length, int height){
       m_width = width;
       m_length = length;
       m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;
};

class StorageBox : public Box {
public:
    StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){
        m_label = label;
    }
private:
    string m_label;
};

int main(){

    const string aLabel = "aLabel";
    StorageBox sb(1, 2, 3, aLabel);
} 

Constructores de clases que tienen herencia múltiple

Si una clase se deriva de varias clases base, los constructores de clase base se invocan en el orden en que se muestran en la declaración de la clase derivada:

#include <iostream>
using namespace std;

class BaseClass1 {
public:
    BaseClass1() {
        cout << "BaseClass1 constructor." << endl;
    }
};
class BaseClass2 {
public:
    BaseClass2() {
        cout << "BaseClass2 constructor." << endl;
    }
};
class BaseClass3{
public:
    BaseClass3() {
        cout << "BaseClass3 constructor." << endl;
    }
};
class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3  {
public:
    DerivedClass() {
        cout << "DerivedClass constructor." << endl;
    }
};

int main() {
    DerivedClass dc;
}

Debería esperar los siguientes resultados:

BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.

Funciones virtuales en constructores

Se recomienda tener cuidado al llamar a funciones virtuales en constructores. Dado que el constructor de clase base se invoca siempre antes del constructor de clase derivada, la función a la que llama en el constructor base es la versión de la clase base, no la versión de la clase derivada. En el ejemplo siguiente, crear una DerivedClass hace que la implementación de BaseClass de print_it() se ejecute antes de que el constructor de DerivedClass haga que la implementación de DerivedClass de print_it() se ejecute:

#include <iostream>
using namespace std;

class BaseClass{
public:
    BaseClass(){
        print_it();
    }
    virtual void print_it() {
        cout << "BaseClass print_it" << endl;
    }
};

class DerivedClass : public BaseClass {
public:
    DerivedClass() {
        print_it();
    }
    virtual void print_it(){
        cout << "Derived Class print_it" << endl;
    }
};

int main() {

    DerivedClass dc;
}

Este es el resultado:

BaseClass print_it
Derived Class print_it

Clases de constructores y compuestas

Las clases que contienen miembros de tipo de clase se conocen como clases compuestas. Cuando se crea un miembro de tipo de clase compuesta, se llama al constructor antes que al propio constructor de la clase. Si una clase contenida carece de un constructor predeterminado, debe utilizar una lista de inicializaciones en el constructor de la clase compuesta. En el ejemplo anterior de StorageBox, si cambia el tipo de la variable miembro m_label a una nueva clase Label, debe llamar al constructor de la clase base e inicializar la variable m_label en el constructor StorageBox:

class Label {
public:
    Label(const string& name, const string& address) { m_name = name; m_address = address; }
    string m_name;
    string m_address;
};

class StorageBox : public Box {
public:
    StorageBox(int width, int length, int height, Label label) 
        : Box(width, length, height), m_label(label){}
private:
    Label m_label;
};

int main(){
// passing a named Label
    Label label1{ "some_name", "some_address" };
    StorageBox sb1(1, 2, 3, label1);

    // passing a temporary label
    StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });

    // passing a temporary label as an initializer list
    StorageBox sb3(1, 2, 3, {"myname", "myaddress"});
}

Constructores que delegan

Un constructor que delega llama a un constructor diferente en la misma clase para realizar algunas de las tareas de inicialización. En el ejemplo siguiente, la clase derivada tiene tres constructores: el segundo constructor delega en el primero, y el tercero delega en el segundo:

#include <iostream>
using namespace std;

class ConstructorDestructor {
public:
    ConstructorDestructor() {
        cout << "ConstructorDestructor default constructor." << endl;
    }
    ConstructorDestructor(int int1) {
        cout << "ConstructorDestructor constructor with 1 int." << endl;
    }
    ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
        cout << "ConstructorDestructor constructor with 2 ints." << endl;
        
        throw exception();
    }
    ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
        cout << "ConstructorDestructor constructor with 3 ints." << endl;
    }
    ~ConstructorDestructor() {
        cout << "ConstructorDestructor destructor." << endl;
    }
};

int main() {
    ConstructorDestructor dc(1, 2, 3);
}

Este es el resultado:

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.

El objeto creado por los constructores se inicializa totalmente en cuanto finaliza cualquiera de los constructores. DerivedContainer(int int1) es correcto, pero DerivedContainer(int int1, int int2) produce un error y se llama al destructor.

class ConstructorDestructor {
public:
    ConstructorDestructor() {
        cout << "ConstructorDestructor default constructor." << endl;
    }
    ConstructorDestructor(int int1) {
        cout << "ConstructorDestructor constructor with 1 int." << endl;
    }
    ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
        cout << "ConstructorDestructor constructor with 2 ints." << endl;
        throw exception();
    }
    ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
        cout << "ConstructorDestructor constructor with 3 ints." << endl;
    }

    ~ConstructorDestructor() {
        cout << "ConstructorDestructor destructor." << endl;
    }
};

int main() {

    try {
        ConstructorDestructor cd{ 1, 2, 3 };
    }
    catch (const exception& ex){
    }
}

Resultado:

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.

Para obtener más información, consulta Inicialización uniforme y constructores de delegación.

Vea también

Referencia

Funciones miembro especiales (C++)