c++ - Brace-or-equal-Initializer en uniones
c++11 g++ (1)
Relacionado: Cómo inicializar un miembro no POD en Union
La norma dice
A lo sumo, un miembro de datos no estáticos de una unión puede tener un inicializador de apoyo o igual
Pero
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p = Point(1,2);
};
#include <iostream>
int main () {
U u;
std::cout << u.p.x_ << ":" << u.p.y_ << std::endl;
}
Imprime 4196960:0
lugar del esperado 1:2
.
Considero esto un error de compilación. ¿Es eso así?
C ++ 11 [class.ctor] / 5 estados:
Un constructor predeterminado para una clase
X
es un constructor de claseX
que se puede llamar sin un argumento. Si no hay un constructor declarado por el usuario para la claseX
, un constructor que no tiene parámetros se declara implícitamente como predeterminado (8.4). Un constructor predeterminado declarado implícitamente es un miembroinline public
de su clase. Un constructor predeterminado predeterminado para la claseX
se define como eliminado si:
X
es una clase similar a una unión que tiene un miembro variante con un constructor predeterminado no trivial,- cualquier miembro de datos no estáticos sin un inicializador de refuerzo o igual es de tipo de referencia,
- cualquier miembro de datos no estáticos no variante de tipo cualificado por const (o matriz del mismo) sin inicializador con refuerzo o igual no tiene un constructor predeterminado proporcionado por el usuario,
X
es una unión y todos sus miembros variantes son de tipo cualificado por const (o conjunto de ellos),X
es una clase no sindicalizada y todos los miembros de cualquier miembro anónimo de la unión son de tipo cualificado por constantes (o una matriz de los mismos),- cualquier clase de base directa o virtual, o miembro de datos no estáticos sin inicializador de refuerzo o igual , tiene clase de clase
M
(o matriz de la misma) yM
no tiene un constructor predeterminado o una resolución de sobrecarga (13.3) aplicada aM
'' el constructor predeterminado de s da como resultado una ambigüedad o una función que se elimina o es inaccesible desde el constructor predeterminado predeterminado, o- cualquier clase de base directa o virtual o miembro de datos no estáticos tiene un tipo con un destructor que se elimina o es inaccesible desde el constructor predeterminado predeterminado.
Un constructor predeterminado es trivial si no es proporcionado por el usuario y si:
- su clase no tiene funciones virtuales (10.3) ni clases de base virtual (10.1), y
- ningún miembro de datos no estáticos de su clase tiene un inicializador de corsé o igual , y
- todas las clases base directas de su clase tienen constructores por defecto triviales, y
- para todos los miembros de datos no estáticos de su clase que son de tipo de clase (o matriz de ellos), cada clase tiene un constructor predeterminado trivial.
De lo contrario, el constructor predeterminado no es trivial .
Dado que el Point
struct en el OP tiene un constructor predeterminado no trivial,
Point() {}
un constructor predeterminado predeterminado para una unión que contenga un miembro de tipo Point
debe definirse como eliminado de acuerdo con el primer punto:
X
es una clase similar a una unión que tiene un miembro variante con un constructor predeterminado no trivial
dando como resultado que el programa presentado en el OP está mal formado.
Sin embargo, el comité parece considerar que esto es un defecto en el caso de que un miembro de un sindicato tenga un inicializador de refuerzo o igual , según el problema de grupo de trabajo central 1623 :
Según el párrafo 12.1 [class.ctor],
Un constructor predeterminado predeterminado para la clase X se define como eliminado si:
X
es una clase similar a una unión que tiene un miembro variante con un constructor predeterminado no trivial,...
X
es una unión y todos sus miembros variantes son de tipo cualificado por const (o conjunto de ellos),
X
es una clase no sindicalizada y todos los miembros de cualquier miembro anónimo de la unión son de tipo cualificado por constantes (o una matriz de los mismos),...
Debido a que la presencia de un inicializador de miembro de datos no estáticos es el equivalente moral de un inicializador de mem , estas reglas probablemente deberían modificarse para no definir el constructor generado como eliminado cuando un miembro de la unión tiene un inicializador de miembro de datos no estático. (Tenga en cuenta las referencias no normativas en 9.5 párrafos 2-3 [class.union] y 7.1.6.1 [dcl.type.cv] que también deberían actualizarse si se cambia esta restricción).
También sería útil agregar un requisito a 9.5 [class.union] que requiera un inicializador de miembro de datos no estáticos o un constructor provisto por el usuario si todos los miembros de la unión tienen tipos constantes.
En una nota más general, ¿por qué se define el constructor predeterminado como eliminado solo porque un miembro tiene un constructor predeterminado no trivial? La propia unión no sabe qué miembro es el activo, y la construcción por defecto no inicializará ningún miembro (suponiendo que no haya un inicializador de refuerzo o igual ). Depende del "propietario" de la unión controlar la vida útil del miembro activo (si existe), y exigir que un constructor provisto por el usuario está forzando un patrón de diseño que no tiene sentido. En la misma línea, ¿por qué se define el destructor predeterminado como eliminado solo porque un miembro tiene un destructor no trivial? Estaría de acuerdo con esta restricción si solo se aplicara cuando la unión también tiene un constructor proporcionado por el usuario.
El número 1623 tiene el estado de "redacción", lo que indica que el comité cree que el problema es probablemente un defecto. ¿Por qué otra cosa permite un inicializador de refuerzo o igual para un miembro del sindicato? - pero aún no ha dedicado el tiempo para determinar la redacción adecuada para una resolución. De hecho, el párrafo es prácticamente el mismo en el borrador actual de C ++ 14 N3936 ([class.ctor] / 4), excepto que el texto "cualquier clase de base directa o virtual o un miembro de datos no estáticos" se sustituye en todas partes por más simple "cualquier subobjeto potencialmente construido".
Aunque el comportamiento de ambos compiladores no es estrictamente conforme, consideraría que Clang se está comportando en el espíritu de la norma. Parece que GCC se confunde con la combinación de un constructor por defecto eliminado y un inicializador de corsé o igual :
- hace un diagnóstico del programa como mal formado en ausencia de la inicialización de corsé o igual ,
con el inicializador de refuerzo o igual y las advertencias máximas, GCC 4.8.2 no realiza ninguna inicialización de la unión en absoluto, e incluso advierte que los miembros se utilizan sin inicializar :
main.cpp: In function ''int main()'': main.cpp:17:39: warning: ''u.U::p.Point::y_'' is used uninitialized in this function [-Wuninitialized] std::cout << u.p.x_ << ":" << u.p.y_ << std::endl; ^ main.cpp:17:22: warning: ''u.U::p.Point::x_'' is used uninitialized in this function [-Wuninitialized] std::cout << u.p.x_ << ":" << u.p.y_ << std::endl; ^
GCC probablemente debería cumplir con el estándar y diagnosticar el programa como mal formado, o emular el comportamiento de Clang y generar un constructor adecuado a partir del inicializador de llave o igual .