poo - ejemplos de programas en c++ pdf
¿Está este comportamiento de inicialización de miembros de C++ bien definido? (2)
Supongamos que tenemos una clase B
que tiene un member
que se inicializa por defecto a 42
. Esta clase sabe cómo imprimir el valor de su member
. (Lo hace en el c''tor):
struct B
{
B() : member(42) { printMember(); }
void printMember() const { std::cout << "value: " << member << std::endl; }
int member;
};
Luego agregamos una clase A
que recibe una referencia constante a B
y le pide a B
que imprima su valor:
struct A
{
A(const B& b) { b.printMember(); }
};
Finalmente agregamos otra clase Aggregate
que agrega una A
y una B
La parte difícil es que el objeto a
del tipo A
se declara antes que el objeto b
tipo B
, pero luego se inicializa utilizando una referencia (¿aún no es válida?) Para b
:
struct Aggregate
{
A a;
B b;
Aggregate() : a(b) { }
};
Considere la salida de la creación de un Aggregate
(agregué algunos registros a ambos c''tor y d''tor de A
y B
) ( Pruébelo en línea ):
a c''tor
value: 0
b c''tor
value: 42
b d''tor
a d''tor
¿Tengo razón al suponer que no es válido inicializar a
con una referencia a una instancia (aún no válida) de b
y que, por lo tanto, este es un comportamiento indefinido?
Edición : Estoy al tanto del orden de inicialización. Esto es lo que me hace luchar. Sé que b
aún no está construido, pero también creo que saber que la dirección futura de b
puede determinarse incluso antes de que se construya b
. Por lo tanto, asumí que podría haber alguna regla de la que no tengo conocimiento que permita al compilador inicializar por defecto los miembros de b
s antes de la construcción de b
o algo así. (Hubiera sido más obvio si el primer valor impreso hubiera sido algo que parece aleatorio en lugar de 0
(el valor predeterminado de int
))
Edición : esta respuesta me ayudó a entender que necesito distinguir entre
- vincular una referencia a un objeto no inicializado (que es válido) y
- acceso por referencia a un objeto no inicializado (que no está definido)
El orden de inicialización de los miembros de la clase es el siguiente.
Desde el estándar CPP (N4713), se resalta la parte relevante:
15.6.2 Inicializando bases y miembros [class.base.init] ...
13 En un constructor no delegante, la inicialización se realiza en el siguiente orden:
(13.1) - Primero, y solo para el constructor de la clase más derivada (6.6.2), las clases de base virtual se inicializan en el orden en que aparecen en un primer recorrido de izquierda a derecha de profundidad del gráfico acíclico de base dirigido Clases, donde "de izquierda a derecha" es el orden de aparición de las clases base en la clase derivada base-especificador-lista.
(13.2) - Entonces, las clases básicas directas se inicializan en el orden de declaración tal como aparecen en la lista-especificador-base (sin importar el orden de los inicializadores de memoria).
(13.3) - Luego, los miembros de datos no estáticos se inicializan en el orden en que fueron declarados en la definición de la clase (de nuevo, independientemente del orden de los inicializadores de memoria).
(13.4) - Finalmente, se ejecuta la declaración compuesta del cuerpo del constructor.
[Nota: El orden de declaración es obligatorio para garantizar que los subobjetos base y miembro se destruyan en el orden inverso de inicialización. "Nota final"
Sí, tienes razón en que es UB, pero por razones diferentes a simplemente almacenar una referencia a un objeto que no se ha construido.
La construcción de los miembros de la clase sucede en el orden de su aparición en la clase. Aunque la dirección de B
no va a cambiar y, técnicamente, puede almacenar una referencia a ella , como señaló @StoryTeller, llamar a b.printMember()
en el constructor con b
que aún no se ha construido es definitivamente UB.