versiones guia español actualizar c++

guia - ¿Por qué C++ requiere un constructor predeterminado proporcionado por el usuario para construir de forma predeterminada un objeto const?



qgis español (4)

Enhorabuena, has inventado un caso en el que no es necesario que haya un constructor definido por el usuario para la declaración de const sin inicializador para que tenga sentido.

Ahora puede hacer una redacción razonable de la regla que cubre su caso pero aún hace que los casos que deberían ser ilegales sean ilegales? ¿Es menos de 5 o 6 párrafos? ¿Es fácil y obvio cómo se debe aplicar en cualquier situación?

Postulo 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 manera que tenga sentido para las personas cuando se lee 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 muy compleja 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? ¿Hay algún código que de otra manera sería muy difícil de escribir o entender que se pueda escribir mucho más simplemente si la regla es más compleja?

El estándar de C ++ (sección 8.5) dice:

Si un programa solicita la inicialización predeterminada de un objeto de un tipo const-calificado T, 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; }


Esto se consideró un defecto (contra todas las versiones del estándar) y fue resuelto por el open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#253 . La nueva redacción de los estados estándar en http://eel.is/c++draft/dcl.init#7

Un tipo de clase T es const-default-constructible si la inicialización por defecto de T invocaría un constructor de T provisto por el usuario (no heredado de una clase base) o si

  • cada miembro directo no variante de datos no estáticos M de T tiene un inicializador de miembros predeterminado o, si M es del tipo de clase X (o matriz del mismo), X es const-default-constructible,
  • si T es una unión con al menos un miembro de datos no estáticos, exactamente un miembro de la variante tiene un inicializador de miembros predeterminado,
  • si T no es una unión, para cada miembro de unión anónimo con al menos un miembro de datos no estáticos (si corresponde), exactamente un miembro de datos no estático tiene un inicializador de miembros predeterminado, y
  • cada clase base potencialmente construida de T es const-default-constructible.

Si un programa requiere la inicialización por defecto de un objeto de un tipo const-calificado T, T será un tipo de clase const-default-construible o una matriz de los mismos.

Esta redacción significa esencialmente que el código obvio funciona. Si inicializas todas tus bases y miembros, puedes decir A const a; independientemente de cómo o si deletreas constructores.

struct A { }; A const a;

gcc lo ha aceptado 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 antes).


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 const de POD que no está inicializado, ¿de qué sirve? Así que creo que el estándar aplica esta regla para que el objeto pueda ser realmente útil.

struct POD { int i; }; POD p1; //uninitialized - but don''t worry we can assign some value later on! p1.i = 10; //assign some value later on! POD p2 = POD(); //initialized const POD p3 = POD(); //initialized const POD p4; //uninitialized - error - as we cannot change it later on!

Pero si haces que la clase no sea POD:

struct nonPOD_A { nonPOD_A() {} //this makes non-POD }; nonPOD_A a1; //initialized const nonPOD_A a2; //initialized

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 en que puedes hacer eso.

struct nonPOD_B { virtual void f() {} //virtual function make it non-POD }; nonPOD_B b1; //initialized const nonPOD_B b2; //initialized

Observe que no POD_B no define el constructor definido por el usuario. Compilarlo Compilará:

Y comente la función virtual, luego da error, como se esperaba:

Bueno, creo que malinterpretaste el pasaje. Primero dice esto (§8.5 / 9):

Si no se especifica ningún inicializador para un objeto, y el objeto es de tipo de clase no POD (posiblemente un CV), el objeto se inicializará por defecto; [...]

Habla sobre el tipo de clase no POD posiblemente cv-calificado . Es decir, el objeto que no es POD se inicializará por defecto si no se especifica un inicializador. ¿Y qué es inicializado por defecto ? Para no POD, la especificación dice (§8.5 / 5),

Para inicializar por defecto un objeto de tipo T significa:
- si T es un tipo de clase no POD (cláusula 9), se llama al constructor predeterminado para T (y la inicialización está mal formada si T no tiene un constructor por defecto accesible);

Simplemente habla sobre el constructor predeterminado de T, independientemente de si el definido por el usuario o el generado por el compilador es irrelevante.

Si está listo para esto, entienda lo que dice la siguiente especificación ((§8.5 / 9),

[...]; si el objeto es de tipo const-qualified, el tipo de clase subyacente tendrá un constructor predeterminado declarado por el usuario.

Entonces, este texto implica que el programa estará mal formado si el objeto es del tipo POD constificado y no hay un inicializador especificado (porque el POD no está inicializado por defecto):

POD p1; //uninitialized - can be useful - hence allowed const POD p2; //uninitialized - never useful - hence not allowed - error

Por cierto, esto compila bien , porque no es POD, y puede ser inicializado por defecto .


Pura especulación de mi parte, pero considere que otros tipos también tienen una restricción similar:

int main() { const int i; // invalid }

Entonces, no solo esta regla es consistente, sino que también (recursivamente) evita la const (sub) objetos unitarios:

struct X { int j; }; struct A { int i; X x; } int main() { const A a; // a.i and a.x.j in unitialized states! }

En cuanto al otro lado de la pregunta (lo que permite tipos con un constructor predeterminado), creo que la idea es que un tipo con un constructor predeterminado proporcionado por el usuario se supone que siempre 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:

struct A { explicit A(int i): initialized(true), i(i) {} // valued constructor A(): initialized(false) {} bool initialized; int i; }; const A a; // class invariant set up for the object // yet we didn''t pay the cost of initializing a.i

Entonces, tal vez podríamos formular una regla como ''al menos un miembro debe ser sensiblemente inicializado en un constructor predeterminado proporcionado por el usuario'', pero eso es demasiado tiempo dedicado a tratar de proteger contra Murphy. C ++ tiende a confiar en el programador en ciertos puntos.