Espacios de nombres
Variantes
Acciones

Constructores y listas de inicializadores de miembros

De cppreference.com
< cpp‎ | language
 
 
 
 

El constructor es una función miembro especial, no estática, de una clase que se usa para inicializar objetos de su tipo de clase.

En la definición del constructor de una clase, la lista de inicializadores de miembros especifica los inicializadores para los subobjetos directos y de bases virtuales, así como de los datos miembro no estáticos. No debe confundirse con std::initializer_list.

Un constructor no debe ser una corrutina.

(desde C++20)

Contenido

[editar] Sintaxis

Los constructores se declaran usando los declaradores de función miembro de la siguiente manera:

nombre-de-la-clase ( lista-de-parámetros(opcional) ) especificación-de-excepción(opcional) atrib(opcional) (1)

Donde el nombre-de-la-clase debe nombrar la clase actual (o la instanciación actual de una plantilla de clase), o, al declararse en un ámbito de espacio de nombres o en una declaración friend, debe ser un nombre de clase calificado.

Los únicos especificadores que se permiten en la sec-decl-especificadores de la declaración de un constructor son friend, inline, constexpr (desde C++11), consteval (desde C++20), y explicit (en particular, no se permite un tipo de retorno). Observa que los calificadores-cv y de referencia tampoco se permiten; la semántica const y volatile de un objeto en proceso de construcción no surte efecto hasta que se completa el constructor de la clase más derivada.

El cuerpo de una definición de función para cualquier constructor, antes de la llave de apertura de la instrucción compuesta, puede incluir la lista de inicializadores de miembros, cuya sintaxis es el carácter de dos puntos :, seguido de una lista separada por comas de uno o más inicializadores de miembros, cada uno de los cuales tiene la siguiente sintaxis:

clase-o-identificador ( lista-de-expresiones(opcional) ) (1)
clase-o-identificador lista-de-inicializadores-entre-llaves (2) (desde C++11)
paquete-de-parámetros ... (3) (desde C++11)
1) Inicializa la base o miembro denominado por clase-o-identificador usando inicialización directa o, si la lista-de-expresiones está vacía, inicialización de un valor.
2) Inicializa la base o miembro denominado por la clase-o-identificador usando inicialización de lista (que se vuelve inicialización de un valor si la lista está vacía e inicialización de agregado al inicializar un agregado).
3) Inicializa múltiples clases base usando una expansión de paquete
clase-o-identificador - Cualquier identificador que denomine un miembro de datos no estático o cualquier nombre de tipo que denomine la clase en sí (para delegar constructores) o una base directa o virtual.
lista-de-expresiones - Lista separada por comas, que puede estar vacía, de los parámetros a pasar al constructor de la base o miembro.
lista-de-inicializadores-entre-llaves - Lista de inicializadores entre llaves separados por comas y listas anidadas de inicializadores entre llaves.
paquete-de-parámetros - Nombre del paquete de parámetros de una plantilla variádica.
struct S 
{
    int n;
 
    S(int);             // declaración del constructor 
    S() : n(7) {}       // definición del constructor
                        // ": n(7)" es la listas de inicializadores
                        // ": n(7) {}" es el cuerpo de la función
};
 
S::S(int x) : n{x} {}   // definición del constructor
                        // ": n{x}" es la listas de inicializadores
int main()
{
    S s;                // llama a S::S()
    S s2(10);           // llama a S::S(int)
}


[editar] Explicación

Los constructores no tienen nombres y no pueden llamarse directamente. Se invocan cuando tiene lugar la inicialización, y se seleccionan de acuerdo con las reglas de inicialización. Los constructores sin especificador explicit son constructores de conversión. Los constructores con un especificador constexpr hacen que su tipo sea un LiteralType. Los constructores que pueden llamarse sin ningún argumento son constructores por defecto. Los constructores que toman otro objeto del mismo tipo que el argumento son constructores de copia y constructores de movimiento.

Antes de que la instrucción compuesta que forma el cuerpo de la función del constructor comience a ejecutarse, finaliza la inicialización de todas las bases directas, bases virtuales y datos miembro no estáticos. La lista de inicializadores de miembros es el lugar donde se puede especificar la inicialización no predeterminada de estos objetos. Para las bases y los miembros de datos que no se pueden inicializar por defecto, como los miembros de referencia y los tipos calificados const, se deben especificar los inicializadores de miembros. No se realiza ninguna inicialización para uniones anónimas o miembros variantes que no tienen un inicializador de miembro.

Los inicializadores donde una clase-o-identificador nombra una clase base virtual se ignoran durante la ejecución de los constructores de cualquier clase que no sea la clase más derivada del objeto que se está construyendo.

Los nombres que aparecen en la lista-de-expresiones o la lista-de-inicializadores-entre-llaves se evalúan en el ámbito del constructor:

class X 
{
    int a, b, i, j;
public:
    const int& r;
 
    X(int i)
      : r(a)    // inicializa a X::r para que se refiera a X::a
      , b{i}    // inicializa a X::b con el valor del parámetro i
      , i(i)    // inicializa a X::i con el valor del parámetro i
      , j(this->i)  // inicializa a X::j con el valor de X::i
    { }
};

Las excepciones que se generan desde los inicializadores de miembros pueden capturarse mediante el bloque try de función.

Las funciones miembro (incluidas las funciones miembro virtuales) se pueden invocar desde los inicializadores de miembro, pero el comportamiento no está definido si no se inicializan todas las bases directas en ese punto.

Para llamadas virtuales (si se inicializan las bases directas en este punto), se aplican las mismas reglas que las reglas para las llamadas virtuales de constructores y destructores: las funciones miembro virtuales se comportan como si el tipo dinámico de *this es el tipo estático de la clase que se está construyendo (el despacho dinámico no se propaga por la jerarquía de herencia) y las llamadas virtuales (pero no las llamadas estáticas) a las funciones miembro virtuales puras tienen comportamientos indefinidos.

Si un miembro de datos no estático tiene un inicializador de miembro por defecto y también aparece en una lista de inicializadores de miembros, entonces se ejecuta la lista de inicializadores de miembros y se ignora el inicializador de miembro por defecto:

struct S 
{
    int n = 42;   // inicializador de miembro por defecto
    S() : n(7) {} // establecerá n a 7, no a 42
};
(desde C++11)


Los miembros de referencia no pueden vincularse a temporales en la lista de inicializadores de miembros:

struct A 
{
    A() : v(42) { }  // ERROR
    const int& v;
};

Nota: lo mismo se aplica al inicializador de miembro por defecto.

Constructor delegador

Si el nombre de la clase misma aparece como clase-o-identificador en la lista de inicializadores de miembros, entonces la lista debe consistir en solamente ese único inicializador de miembro; tal constructor se conoce como el constructor delegador, y el constructor seleccionado por ese único miembro de la lista de inicializadores de miembros es el constructor objetivo.

En este caso, el constructor objetivo se selecciona por resolución de sobrecarga y se ejecuta primero, luego el control se devuelve al constructor delegador y su cuerpo se ejecuta.

Los constructores delegadores no pueden ser recursivos.

class Foo 
{
public: 
    Foo(char x, int y) {}
    Foo(int y) : Foo('a', y) {} // Foo(int) delega a Foo(char,int)
};

Heredar Constructores

Véase declaración using.

(desde C++11)


[editar] Orden de inicialización

El orden de los inicializadores de miembros en la lista es irrelevante: el orden actual de inicialización es de la siguiente manera:

1) Si el constructor es para la clase más derivada, las clases base virtuales se inicializan en el orden en el que aparecen en el recorrido a profundidad, de izquierda a derecha de las declaraciones de las clases base (de izquierda a derecha se refiere a cómo aparecen en las listas de especificadores de base).
2) Luego se inicializan las clases base directas en orden de izquierda a derecha como aparecen en la lista de especificadores de base de esta clase.
3) Luego se inicializan los datos miembro no estáticos en el orden de declaración en la definición de la clase.
4) Finalmente, se ejecuta el cuerpo del constructor.

(Nota: si el orden de inicialización se controlara por el orden de aparición en la lista de inicializadores de miembros de distintos constructores, entonces el destructor no sería capaz de garantizar que el orden de destrucción fuera el inverso del orden de construcción)

[editar] Ejemplo

#include <fstream>
#include <string>
#include <mutex>
 
struct Base 
{
    int n;
};   
 
struct Clase : public Base
{
    unsigned char x;
    unsigned char y;
    std::mutex m;
    std::lock_guard<std::mutex> lg;
    std::fstream f;
    std::string s;
 
    Clase ( int x )
        : Base { 123 },   // inicializa clase base
        x ( x ),        // miembro x se inicializa con el parámetro x
        y { 0 },        // y se inicializa a 0
        f{"test.cc", std::ios::app}, // esto toma lugar después que se inicializan
                                     // m y lg
        s(__func__),    //__func__ está disponible porque la inicialización de lista
                        // es parte del constructor
        lg ( m ),       // lg usa a m, que ya está inicializada
        m{}             // m se inicializa antes que lg aun cuando aquí aparece el último
    {}                  // instrucción compuesta vacía 
 
    Clase ( double a )
        : y ( a+1 ),
        x ( y ),        // x se inicializará antes que y, aquí su valor es indeterminado
        lg ( m )
    {} // el constructor de la clase base no aparece en la lista.
       // Es inicializado por defecto (no es los mismo que si se hubiera usado Base(),
       // que se usa inicialización de un valor)
 
    Clase()
    try                 // el bloque try de función empieza antes que el cuerpo
                        // de la función, que incluye la lista de inicialización
      : Clase( 0.0 )    // constructor delegado
    {
        // ...
    }
    catch (...)
    {
        // ocurrió un error durante la inicialización
    }
};
 
int main() {
    Clase c;
    Clase c1(1);
    Clase c2(0.1);
}


[editar] Informes de defectos

Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.

ID Aplicado a Comportamiento según lo publicado Comportamiento correcto
CWG 194 C++98 la sintaxis de declaración de constructor solo permitía
como máximo un especificador de función (por ejemplo, un
constructor no se podía declarar inline explicit)
se permiten múltiples
especificadores de función
CWG 257 C++98 no se especificó sin una clase abstracta debería proporcionar
inicializadores de miembros para sus clases base virtuales
se especifica como no requerido
y dichos incializadores de miembros se
ignoran durante la ejecución
CWG 263 C++98 la sintaxis de declaración del constructor prohibía
que los constructores fueran amigos
Se permite que los
constructores sena amigos
CWG 1345 C++98 los miembros de unión anónima sin inicializadores de
miembros por defecto se inicializaban por defecto
no están incializados
CWG 1435 C++98 el significado de 'nombre de clase' en la sintaxis
de la declaración del constructor no estaba claro
cambió la sintaxis a una sintaxis
de declaración de función especializada
CWG 1696 C++14 Los miembros de referencia podían inicializarse
como temporales (cuya duración terminaría al final
del constructor)
Tal inicialización está mal formada

[editar] Referencias

  • El estándar C++20 (ISO/IEC 14882:2020):
  • 11.4.4 Constructors [class.ctor]
  • 11.10.2 Initializing bases and members [class.base.init]
  • El estándar C++17 (ISO/IEC 14882:2017):
  • 15.1 Constructors [class.ctor]
  • 15.6.2 Initializing bases and members [class.base.init]
  • El estándar C++14 (ISO/IEC 14882:2014):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]
  • El estándar C++11 (ISO/IEC 14882:2011):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]
  • El estándar C++98 (ISO/IEC 14882:1998):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]

[editar] Véase también