Constructores y listas de inicializadores de miembros
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) | |||||||
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 delegadorSi 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 ConstructoresVéase declaración |
(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:
(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]