El estándar C ++ (sección 8.5) dice:
Si un programa solicita la inicialización predeterminada de un objeto de tipo T calificado const, T será un tipo de clase con un constructor predeterminado proporcionado por el usuario.
¿Por qué? No puedo pensar en ninguna razón por la que se requiera un constructor proporcionado por el usuario en este caso.
struct B{
B():x(42){}
int doSomeStuff() const{return x;}
int x;
};
struct A{
A(){}//other than "because the standard says so", why is this line required?
B b;//not required for this example, just to illustrate
//how this situation isn't totally useless
};
int main(){
const A a;
}
a
, pero gcc-4.3.4 la acepta incluso cuando lo haga (consulte ideone.com/uHvFS )a
. Comeau produce un error "const variable" a "requiere un inicializador - la clase" A "no tiene un constructor predeterminado declarado explícitamente" si la línea está comentada.const A a{}
:)Respuestas:
Esto se consideró un defecto (contra todas las versiones del estándar) y fue resuelto por el Defecto 253 del Grupo de Trabajo Central (CWG) . La nueva redacción de los estados estándar en http://eel.is/c++draft/dcl.init#7
Esta redacción esencialmente significa que el código obvio funciona. Si inicializa todas sus bases y miembros, puede decir
A const a;
independientemente de cómo o si escribe los constructores.gcc ha aceptado esto desde 4.6.4. clang ha aceptado esto desde 3.9.0. Visual Studio también acepta esto (al menos en 2017, no estoy seguro si será antes).
fuente
struct A { int n; A() = default; }; const A a;
mientras se permitestruct B { int n; B() {} }; const B b;
porque la nueva redacción aún dice "proporcionado por el usuario" no "declarado por el usuario" y me quedo rascándome la cabeza por qué el comité eligió excluir los constructores predeterminados explícitamente predeterminados de este DR, lo que nos obliga a hacer nuestras clases no son triviales si queremos objetos constantes con miembros no inicializados.MyPOD
ser un PODstruct
,static MyPOD x;
- confiando en la inicialización cero (¿es la correcta?) Para establecer la (s) variable (s) miembro de manera apropiada - compila, perostatic const MyPOD x;
no lo hace. ¿Existe alguna posibilidad de que se solucione?La razón es que si la clase no tiene un constructor definido por el usuario, entonces puede ser POD y la clase POD no se inicializa por defecto. Entonces, si declaras un objeto constante de POD que no está inicializado, ¿para qué sirve? Así que creo que el Estándar hace cumplir esta regla para que el objeto pueda ser realmente útil.
Pero si hace que la clase no sea POD:
Tenga en cuenta la diferencia entre POD y no POD.
El constructor definido por el usuario es una forma de hacer que la clase no sea POD. Hay varias formas de hacerlo.
Observe que nonPOD_B no define un constructor definido por el usuario. Compílelo. Compilará:
Y comenta la función virtual, luego da error, como se esperaba:
Bueno, creo que no entendiste el pasaje. Primero dice esto (§8.5 / 9):
Habla de una clase no POD posiblemente de tipo calificado para cv . Es decir, el objeto que no es POD se inicializará por defecto si no se especifica ningún inicializador. ¿Y qué se inicializa por defecto ? Para no POD, la especificación dice (§8.5 / 5),
Simplemente habla del constructor predeterminado de T, ya sea que sea definido por el usuario o generado por el compilador es irrelevante.
Si tiene claro esto, entienda lo que dice la siguiente especificación ((§8.5 / 9),
Entonces, este texto implica que el programa estará mal formado si el objeto es de tipo POD calificado const , y no hay un inicializador especificado (porque los POD no están inicializados por defecto):
Por cierto, esto se compila bien , porque no es POD y puede inicializarse por defecto .
fuente
nonPOD_B
no tiene un constructor predeterminado proporcionado por el usuario, por lo que la líneaconst nonPOD_B b2
no está permitida.B
en la pregunta). Pero el constructor predeterminado proporcionado por el usuario sigue siendo necesario en ese caso.const
objeto que no es POD se inicialice llamando al constructor predeterminado generado por el compilador.Pura especulación de mi parte, pero considere que otros tipos también tienen una restricción similar:
Entonces, esta regla no solo es consistente, sino que también (recursivamente) previene
const
(sub) objetos unitarios :En cuanto al otro lado de la pregunta (permitiéndolo para tipos con un constructor predeterminado), creo que la idea es que se supone que un tipo con un constructor predeterminado proporcionado por el usuario siempre debe estar en algún estado sensible después de la construcción. Tenga en cuenta que las reglas, tal como están, permiten lo siguiente:
Entonces tal vez podríamos formular una regla como 'al menos un miembro debe inicializarse con sensatez en un constructor predeterminado proporcionado por el usuario', pero eso es demasiado tiempo para tratar de protegerse contra Murphy. C ++ tiende a confiar en el programador en ciertos puntos.
fuente
A(){}
, el error desaparecerá, por lo que no evitará nada. La regla no funciona de forma recursiva,X(){}
nunca es necesaria para ese ejemplo.Estaba viendo la charla de Timur Doumler en Meeting C ++ 2018 y finalmente me di cuenta de por qué el estándar requiere un constructor proporcionado por el usuario aquí, no simplemente uno declarado por el usuario. Tiene que ver con las reglas para la inicialización de valores.
Considere dos clases:
A
tiene un constructor declarado por el usuario ,B
tiene un constructor proporcionado por el usuario :A primera vista, podría pensar que estos dos constructores se comportarán igual. Pero vea cómo la inicialización de valores se comporta de manera diferente, mientras que solo la inicialización predeterminada se comporta de la misma manera:
A a;
es la inicialización predeterminada: el miembroint x
no está inicializado.B b;
es la inicialización predeterminada: el miembroint x
no está inicializado.A a{};
es la inicialización del valor: el miembroint x
se inicializa a cero .B b{};
es la inicialización del valor: el miembroint x
no está inicializado.Ahora mira lo que sucede cuando agregamos
const
:const A a;
es la inicialización predeterminada: está mal formada debido a la regla citada en la pregunta.const B b;
es la inicialización predeterminada: el miembroint x
no está inicializado.const A a{};
es la inicialización del valor: el miembroint x
se inicializa a cero .const B b{};
es la inicialización del valor: el miembroint x
no está inicializado.Un
const
escalar no inicializado (por ejemplo, elint x
miembro) sería inútil: escribir en él está mal formado (porque lo estáconst
) y leer desde él es UB (porque tiene un valor indeterminado). Así que esta regla impide crear tal cosa, por lo que obligó a bien agregar un inicializador o opt-in para el comportamiento peligroso mediante la adición de un constructor proporcionado por el usuario.Creo que sería bueno tener un atributo como
[[uninitialized]]
para decirle al compilador cuando intencionalmente no está inicializando un objeto. Entonces no estaríamos obligados a hacer que nuestra clase no sea trivialmente constructible por defecto para sortear este caso de esquina. Este atributo se ha propuesto en realidad , pero al igual que todos los demás atributos estándar, no exige ningún comportamiento normativo, siendo simplemente una pista para el compilador.fuente
Felicitaciones, ha inventado un caso en el que no es necesario que haya ningún constructor definido por el usuario para que la
const
declaración sin inicializador tenga sentido.Ahora, ¿puede pensar en una nueva redacción razonable de la regla que cubre su caso pero que aún hace que los casos que deberían ser ilegales sean ilegales? ¿Tiene menos de 5 o 6 párrafos? ¿Es fácil y obvio cómo se debe aplicar en cualquier situación?
Propongo que crear una regla que permita que la declaración que creó tenga sentido es realmente difícil, y asegurarse de que la regla se pueda aplicar de una manera que tenga sentido para las personas cuando leer el código es aún más difícil. Preferiría una regla algo restrictiva que fuera lo correcto en la mayoría de los casos a una regla compleja y con muchos matices que era difícil de entender y aplicar.
La pregunta es, ¿hay alguna razón de peso para que la regla sea más compleja? ¿Existe algún código que de otro modo sería muy difícil de escribir o entender que se pueda escribir de forma mucho más sencilla si la regla es más compleja?
fuente
const POD x;
ilegal al igualconst int x;
que ilegal (lo cual tiene sentido, porque esto es inútil para un POD), peroconst NonPOD x;
legal (lo cual tiene sentido, porque podría tener subobjetos que contengan constructores / destructores útiles, o tener un constructor / destructor útil en sí mismo) .struct NonPod { std::string s; }; const NonPod x;
y da un error cuando NonPod esstruct NonPod { int i; std::string s; }; const NonPod x;