resueltos programacion poo orientada objetos libro ejercicios ejemplos ejemplo constructores codigo clases avanzada c++ c++14 language-lawyer list-initialization aggregate-initialization

c++ - programacion - Constructor por defecto eliminado. Todavía se pueden crear objetos... a veces



programacion orientada a objetos c++ ejemplos (3)

Al ver las cosas de esta manera, es fácil decir que hay un caos total y completo en la forma en que se inicializa un objeto.

La gran diferencia proviene del tipo de foo : si es un tipo agregado o no.

Es un agregado si tiene:

  • ningún constructor proporcionado por el usuario (una función eliminada o predeterminada no cuenta como proporcionada por el usuario),
  • No hay miembros de datos no estáticos privados o protegidos,
  • no hay inicializadores brace o iguales para miembros de datos no estáticos (desde c ++ 11 hasta (revertido) c ++ 14)
  • no hay clases de base,
  • No hay funciones de miembro virtual.

Asi que:

  • en escenarios ABDE: foo es un agregado
  • en los escenarios C: foo no es un agregado
  • escenario F:
    • en c ++ 11 no es un agregado.
    • en c ++ 14 es un agregado.
    • g ++ no lo ha implementado y aún lo trata como un no agregado incluso en C ++ 14.
      • 4.9 no implementa esto.
      • 5.2.0 hace
      • 5.2.1 ubuntu no (tal vez una regresión)

Los efectos de la inicialización de lista de un objeto de tipo T son:

  • ...
  • Si T es un tipo agregado, se realiza la inicialización agregada. Esto se ocupa de los escenarios ABDE (y F en C ++ 14)
  • De lo contrario los constructores de T se consideran en dos fases:
    • Todos los constructores que toman std :: initializer_list ...
    • de lo contrario [...] todos los constructores de T participan en la resolución de sobrecarga [...] Esto se encarga de C (y F en C ++ 11)
  • ...

:

Inicialización agregada de un objeto de tipo T (escenarios ABDE (F c ++ 14)):

  • Cada miembro de la clase no estática, en orden de aparición en la definición de clase, se inicializa con copia de la cláusula correspondiente de la lista de inicializador. (referencia de matriz omitida)

TL; DR

Todas estas reglas todavía pueden parecer muy complicadas y provocar dolor de cabeza. Personalmente simplifico esto por mí mismo (si me disparo en el pie, así será: supongo que pasaré 2 días en el hospital en lugar de tener un par de docenas de días de dolores de cabeza):

  • para un agregado, cada miembro de datos se inicializa desde los elementos del inicializador de lista
  • de lo contrario llama constructor

¿Esto no supera todo el propósito de un constructor eliminado?

Bueno, no lo sé, pero la solución es hacer que foo no sea un agregado. La forma más general que no agrega gastos generales y no cambia la sintaxis utilizada del objeto es hacer que se herede de una estructura vacía:

struct dummy_t {}; struct foo : dummy_t { foo() = delete; }; foo f{}; // ERROR call to deleted constructor

En algunas situaciones (sin miembros no estáticos, supongo), una alternativa sería eliminar el destructor (esto hará que el objeto no sea instanciable en ningún contexto):

struct foo { ~foo() = delete; }; foo f{}; // ERROR use of deleted function `foo::~foo()`

Esta respuesta utiliza información obtenida de:

Muchas gracias a @M.M que ayudó a corregir y mejorar esta publicación.

La visión ingenua, optimista y oh ... tan incorrecta de la sintaxis de inicialización uniforme de c ++ 11

Pensé que ya que los objetos de tipo definido por el usuario de C ++ 11 deberían construirse con la nueva sintaxis {...} lugar de la sintaxis antigua (...) (excepto para el constructor sobrecargado para std::initializer_list y parámetros similares (por ejemplo, std::vector : size ctor vs 1 elem init_list ctor)).

Los beneficios son: no hay conversiones implícitas limitadas, no hay problema con el análisis más molesto, la consistencia (?). No vi ningún problema porque pensé que son lo mismo (excepto el ejemplo dado).

Pero no lo son.

Un cuento de pura locura.

El {} llama al constructor por defecto.

... Excepto cuando:

  • el constructor por defecto se elimina y
  • No hay otros constructores definidos.

¿Entonces parece que el valor inicializa el objeto? ... Incluso si el objeto ha eliminado el constructor predeterminado, el {} puede crear un objeto. ¿Esto no supera todo el propósito de un constructor eliminado?

...Excepto cuando:

  • el objeto tiene un constructor por defecto eliminado y
  • otro (s) constructor (es) definido (s).

Entonces falla con la call to deleted constructor .

...Excepto cuando:

  • el objeto tiene un constructor eliminado y
  • ningún otro constructor definido y
  • al menos un miembro de datos no estáticos.

Luego falla con inicializadores de campo faltantes.

Pero entonces puedes usar {value} para construir el objeto.

Ok, quizás esto es lo mismo que la primera excepción (valor init el objeto)

...Excepto cuando:

  • la clase tiene un constructor eliminado
  • y al menos uno de los miembros de datos en clase predeterminado por inicializado.

Entonces ni {} ni {value} pueden crear un objeto.

Estoy seguro de que me perdí algunos La ironía es que se llama sintaxis de inicialización uniforme . Repito : sintaxis de inicialización UNIFORME .

¿Qué es esta locura?

Escenario A

Constructor por defecto eliminado:

struct foo { foo() = delete; }; // All bellow OK (no errors, no warnings) foo f = foo{}; foo f = {}; foo f{}; // will use only this from now on.

Escenario B

Constructor por defecto eliminado, otros constructores eliminados

struct foo { foo() = delete; foo(int) = delete; }; foo f{}; // OK

Escenario C

Constructor por defecto eliminado, otros constructores definidos

struct foo { foo() = delete; foo(int) {}; }; foo f{}; // error call to deleted constructor

Escenario D

Constructor predeterminado eliminado, ningún otro constructor definido, miembro de datos

struct foo { int a; foo() = delete; }; foo f{}; // error use of deleted function foo::foo() foo f{3}; // OK

Escenario E

Constructor predeterminado eliminado, constructor T eliminado, miembro de datos T

struct foo { int a; foo() = delete; foo(int) = delete; }; foo f{}; // ERROR: missing initializer foo f{3}; // OK

Escenario F

Constructor predeterminado eliminado, inicializadores de miembros de datos en clase

struct foo { int a = 3; foo() = delete; }; /* Fa */ foo f{}; // ERROR: use of deleted function `foo::foo()` /* Fb */ foo f{3}; // ERROR: no matching function to call `foo::foo(init list)`


Estos casos relacionados con la inicialización de agregados son contraintuitivos para la mayoría y fueron el tema de la propuesta p1008: prohibir agregados con constructores declarados por el usuario que dicen:

C ++ actualmente permite que algunos tipos con constructores declarados por el usuario se inicialicen a través de la inicialización agregada, omitiendo esos constructores. El resultado es un código sorprendente, confuso y con errores. Este documento propone una solución que hace que la semántica de inicialización en C ++ sea más segura, más uniforme y más fácil de enseñar. También discutimos los cambios de última hora que esta solución introduce

e introduce algunos ejemplos, que se superponen muy bien con los casos que presenta:

struct X { X() = delete; }; int main() { X x1; // ill-formed - default c’tor is deleted X x2{}; // compiles! }

Claramente, la intención del constructor eliminado es evitar que el usuario inicialice la clase. Sin embargo, al contrario de la intuición, esto no funciona: el usuario aún puede inicializar X mediante la inicialización agregada porque esto omite completamente a los constructores. El autor podría incluso eliminar explícitamente todo el constructor predeterminado, copiar y mover, y aún así no evitar que el código del cliente cree una instancia de X a través de la inicialización agregada como se indicó anteriormente. La mayoría de los desarrolladores de C ++ están sorprendidos por el comportamiento actual cuando se muestra este código. El autor de la clase X podría alternativamente considerar la posibilidad de que el constructor predeterminado sea privado. Pero si a este constructor se le da una definición predeterminada, esto nuevamente no evita la inicialización agregada (y por lo tanto, la creación de instancias) de la clase:

struct X { private: X() = default; }; int main() { X x1; // ill-formed - default c’tor is private X x2{}; // compiles! }

Debido a las reglas actuales, la inicialización agregada nos permite "construir por defecto" una clase, incluso si no es, de hecho, constructiva por defecto:

static_assert(!std::is_default_constructible_v<X>);

pasaría por ambas definiciones de X arriba.

...

Los cambios propuestos son:

Modifique [dcl.init.aggr] el párrafo 1 de la siguiente manera:

Un agregado es una matriz o una clase (Cláusula 12) con

  • ningún usuario, explícito , u̲s̲er̲-̲d̲e̲c̲l̲a̲r̲e̲d̲ o constructores heredados (15.1),

  • no miembros privados o protegidos de datos no estáticos (Cláusula 14),

  • sin funciones virtuales (13.3), y

  • no hay clases básicas virtuales, privadas o protegidas (13.1).

Modifique [dcl.init.aggr] el párrafo 17 de la siguiente manera:

[Nota: una matriz agregada o una clase agregada puede contener elementos de una clase >> tipo con un proporcionado por el usuario u̲s̲e̲r̲-̲d̲e̲c̲l̲a̲r̲e̲d̲ constructor (15.1). La inicialización de >> estos objetos agregados se describe en 15.6.1. "Nota final"

Agregue lo siguiente a [diff.cpp17] en el Anexo C, sección C.5 C ++ e ISO C ++ 2017:

C.5.6 Cláusula 11: declaradores [diff.cpp17.dcl.decl]

Subcláusula afectada : [dcl.init.aggr]
Cambio : una clase que tiene constructores declarados por el usuario nunca es un agregado.
Justificación : eliminar la inicialización agregada potencialmente propensa a errores que puede aplicarse sin resistir los constructores declarados de una clase.
Efecto en la característica original : el código válido de C ++ 2017 que agrega-inicializa un tipo con un constructor declarado por el usuario puede estar mal formado o tener una semántica diferente en esta Norma Internacional.

Seguido de ejemplos que omito.

La propuesta fue aceptada y combinada en C ++ 20 , podemos encontrar el último borrador aquí que contiene estos cambios y podemos ver los cambios en [dcl.init.aggr]p1.1 y [dcl.init.aggr]p17 y C ++ 17 declaraciones dif .

Así que esto debería ser arreglado en C ++ 20 adelante.


Lo que te está fastidiando es la inicialización agregada .

Como usted dice, hay ventajas e inconvenientes en el uso de la inicialización de listas. (El término C ++ Standard no utiliza el término "inicialización uniforme").

Uno de los inconvenientes es que la inicialización de la lista se comporta de manera diferente para los agregados que para los no agregados. Además, la definición de agregados cambia ligeramente con cada Estándar.

Los agregados no se crean a través de un constructor. (Técnicamente podrían serlo, pero esta es una buena manera de pensarlo). En su lugar, al crear un agregado, se asigna memoria y luego cada miembro se inicializa en orden de acuerdo con lo que hay en el inicializador de la lista.

Los no agregados se crean a través de constructores, y en ese caso los miembros del inicializador de lista son argumentos de constructor.

En realidad, hay un defecto de diseño en lo anterior: si tenemos T t1; T t2{t1}; T t1; T t2{t1}; , entonces la intención es realizar copia-construcción. Sin embargo, (antes de C ++ 14) si T es un agregado, en su lugar se produce la inicialización agregada y el primer miembro de t2 se inicializa con t1 .

Esta falla se corrigió en un informe de defectos que modificó C ++ 14, por lo que a partir de ahora se verifica la construcción de la copia antes de pasar a la inicialización agregada.

La definición de agregado de C ++ 14 es:

Un agregado es una matriz o una clase (Cláusula 9) sin constructores proporcionados por el usuario (12.1), sin miembros de datos no estáticos protegidos o privados (Cláusula 11), sin clases base (Cláusula 10) y sin funciones virtuales (10.3) ).

En C ++ 11, un valor predeterminado para un miembro no estático significaba que una clase no era un agregado; sin embargo eso fue cambiado para C ++ 14. Proporcionado por el usuario significa declarado por el usuario, pero no = default o = delete .

Si desea asegurarse de que la llamada de su constructor nunca realice accidentalmente la inicialización agregada, entonces tiene que usar ( ) lugar de { } , y evitar los MVP de otras maneras.