resueltos programacion poo ejercicios ejemplos ejemplo constructores codigo clases c++ initialization c++17 list-initialization

programacion - ¿La extensión de C++ 17 a la inicialización agregada ha hecho que la inicialización de llaves sea peligrosa?



constructores c++ (2)

La estructura D y la estructura E representan dos tipos completamente no relacionados.

Pero no son tipos "completamente sin relación". Ambos tienen el mismo tipo de clase base. Esto significa que cada D es implícitamente convertible a B Y por lo tanto cada D es una B Así haciendo E e{d}; no es diferente de E e{b}; en términos de la operación invocada.

No se puede desactivar la conversión implícita a clases base.

Si esto realmente le molesta, la única solución es evitar la inicialización agregada proporcionando un constructor (es) apropiado (s) que reenvíe los valores a los miembros.

En cuanto a si esto hace que la inicialización agregada sea más peligrosa, no lo creo. Podrías reproducir las circunstancias anteriores con estas estructuras:

struct B { int i; }; struct D { B b; char j; operator B() {return b;} }; struct E { B b; float k; };

Así que algo de esta naturaleza siempre fue una posibilidad. No creo que el uso de la conversión de clase base implícita lo haga mucho "peor".

Una pregunta más profunda es por qué un usuario intentó inicializar una E con una D para empezar.

La idea de que puedes convertir silenciosamente de un círculo a un rectángulo, eso para mí es un problema.

Tendrías el mismo problema si hicieras esto:

struct rectangle { rectangle(point p); int sx; int sy; point p; };

No solo puedes realizar el rectangle r{c}; pero el rectangle r(c) .

Su problema es que está utilizando la herencia de forma incorrecta. Estás diciendo cosas sobre la relación entre circle , rectangle y point que no quieres decir. Y por lo tanto, el compilador te permite hacer cosas que no querías hacer.

Si hubiera utilizado la contención en lugar de la herencia, esto no sería un problema:

struct point { int x; int y; }; struct circle { point center; int r; }; struct rectangle { point top_left; int sx; int sy; }; void move( point& p ); void f( circle c ) { move( c ); // Error, as it should, since a circle is not a point. rectangle r1( c ); // Error, as it should be rectangle r2{ c }; // Error, as it should be. }

Cualquiera de los circle es siempre un point , o nunca es un point . Estás tratando de hacer un point veces y no otros. Eso es lógicamente incoherente. Si crea tipos lógicamente incoherentes, puede escribir código lógicamente incoherente.

La idea de que puedes convertir silenciosamente de un círculo a un rectángulo, eso para mí es un problema.

Esto trae a colación un punto importante. La conversión, estrictamente hablando, se ve así:

circle cr = ... rectangle rect = cr;

Eso está mal formado. Cuando haces rectangle rect = {cr}; , no estás haciendo una conversión. Usted está invocando explícitamente la inicialización de lista, que para un agregado generalmente provocará la inicialización agregada.

Ahora, lista-inicialización sin duda puede realizar una conversión. Pero dado meramente D d = {e}; , uno no debe esperar que esto signifique que está realizando una conversión de una e a una D Estás iniciando una lista de un objeto de tipo D con una e . Eso puede realizar la conversión si E es convertible a D , pero esta inicialización aún puede ser válida si los formularios de inicialización de lista sin conversión también pueden funcionar.

Por lo tanto, es incorrecto decir que esta característica hace que el circle convierta en un rectangle .

Parece que hay un consenso general de que la inicialización con corsé debería ser preferible a otras formas de inicialización, sin embargo, desde la introducción de la extensión C ++ 17 a la inicialización agregada parece existir un riesgo de conversiones no intencionadas. Considere el siguiente código:

struct B { int i; }; struct D : B { char j; }; struct E : B { float k; }; void f( const D& d ) { E e1 = d; // error C2440: ''initializing'': cannot convert from ''D'' to ''E'' E e2( d ); // error C2440: ''initializing'': cannot convert from ''D'' to ''E'' E e3{ d }; // OK in C++17 ??? } struct F { F( D d ) : e{ d } {} // OK in C++17 ??? E e; };

En el código anterior, la struct D y la struct E representan dos tipos completamente no relacionados. Por lo tanto, me sorprende que a partir de C ++ 17 se pueda "convertir" de un tipo a otro sin previo aviso si utiliza la inicialización de llaves (agregados).

¿Qué recomendarías para evitar este tipo de conversiones accidentales? ¿O me estoy perdiendo algo?

PD: el código anterior se probó en Clang, GCC y el último VC ++, todos son iguales.

Actualización: En respuesta a la respuesta de Nicol. Considere un ejemplo más práctico:

struct point { int x; int y; }; struct circle : point { int r; }; struct rectangle : point { int sx; int sy; }; void move( point& p ); void f( circle c ) { move( c ); // OK, makes sense rectangle r1( c ); // Error, as it should be rectangle r2{ c }; // OK ??? }

Puedo entender que puedes ver un circle como un point , porque el circle tiene un point como clase base, pero la idea de que puedes convertir silenciosamente de un círculo a un rectángulo, eso es un problema para mí.

Actualización 2: Debido a que mi mala elección de nombre de clase parece estar nublando el problema para algunos.

struct shape { int x; int y; }; struct circle : shape { int r; }; struct rectangle : shape { int sx; int sy; }; void move( shape& p ); void f( circle c ) { move( c ); // OK, makes sense rectangle r1( c ); // Error, as it should be rectangle r2{ c }; // OK ??? }


Esto no es nuevo en C ++ 17. La inicialización agregada siempre le permitió dejar de lado a los miembros (que se inicializarían desde una lista de inicializadores vacía, C++11 ):

struct X { int i, j; }; X x{42}; // ok in C++11

Es justo ahora que hay más tipos de cosas que se pueden dejar, ya que hay más tipos de cosas que se pueden incluir.

gcc y clang al menos proporcionarán una advertencia a través de -Wmissing-field-initializers (es parte de -Wextra ) que indicará que falta algo. Si esta es una gran preocupación, simplemente compile con esa advertencia habilitada (y, posiblemente, actualizada a un error):

<source>: In function ''void f(const D&)'': <source>:9:11: warning: missing initializer for member ''E::k'' [-Wmissing-field-initializers] E e3{ d }; // OK in C++17 ??? ^ <source>: In constructor ''F::F(D)'': <source>:14:19: warning: missing initializer for member ''E::k'' [-Wmissing-field-initializers] F( D d ) : e{ d } {} // OK in C++17 ??? ^

Más directo sería simplemente agregar un constructor a estos tipos para que dejen de ser agregados. No tienes que usar la inicialización agregada, después de todo.