C ++ 11 unión anónima con miembros no triviales

Estoy actualizando una estructura mía y quería agregarle un miembro std :: string. La estructura original se ve así:

struct Value { uint64_t lastUpdated; union { uint64_t ui; int64_t i; float f; bool b; }; }; 

Simplemente agregar un miembro std :: string a la unión, por supuesto, causa un error de comstackción, porque uno normalmente necesitaría agregar los constructores no triviales del objeto. En el caso de std :: string (texto de informit.com)

Como std :: string define las seis funciones miembro especiales, U tendrá un constructor predeterminado, constructor de copia, operador de asignación de copia, constructor de movimiento, destructor de asignación de movimiento implícitamente eliminados. Efectivamente, esto significa que no puede crear instancias de U a menos que defina explícitamente algunas o todas las funciones especiales de miembros.

Luego, el sitio web proporciona el siguiente código de muestra:

 union U { int a; int b; string s; U(); ~U(); }; 

Sin embargo, estoy usando una unión anónima dentro de una estructura. Le pregunté a ## C ++ en freenode y me dijeron que la forma correcta de hacerlo era poner el constructor en la estructura y me dio este código de ejemplo:

 #include  struct Point { Point() {} Point(int x, int y): x_(x), y_(y) {} int x_, y_; }; struct Foo { Foo() { new(&p) Point(); } union { int z; double w; Point p; }; }; int main(void) { } 

Pero a partir de ahí no puedo imaginar cómo hacer que el rest de las funciones especiales que std :: string necesita se definan, y además, no estoy del todo claro sobre cómo funciona el ctor en ese ejemplo.

¿Puedo conseguir que alguien me explique esto un poco más claro?

No hay necesidad de colocar nuevos aquí.

Los miembros de la variante no serán inicializados por el constructor generado por el comstackdor, pero no debería haber problemas para elegir uno e inicializarlo usando la lista de inicializadores del ctor normal. Los miembros declarados dentro de uniones anónimas son en realidad miembros de la clase contenedora, y pueden inicializarse en el constructor de la clase contenedora.

Este comportamiento se describe en la sección 9.5. [class.union] :

Una clase sindical es una unión o una clase que tiene una unión anónima como miembro directo. Una clase X tipo sindical tiene un conjunto de miembros variantes . Si X es una unión, sus miembros variantes son los miembros de datos no estáticos; de lo contrario, sus miembros variantes son los miembros de datos no estáticos de todas las uniones anónimas que son miembros de X

y en la sección 12.6.2 [class.base.init] :

Un inicializador de ctor puede inicializar un miembro de variante de la clase del constructor. Si un inicializador de ctor especifica más de un inicializador de memoria para el mismo miembro o para la misma clase base, el inicializador de ctor está mal formado.

Entonces el código puede ser simple:

 #include  struct Point { Point() {} Point(int x, int y): x_(x), y_(y) {} int x_, y_; }; struct Foo { Foo() : p() {} // usual everyday initialization in the ctor-initializer union { int z; double w; Point p; }; }; int main(void) { } 

Por supuesto, la colocación nueva todavía se debe usar cuando se vivifica un miembro de variante distinto del otro inicializado en el constructor.

Ese new (&p) Point() ejemplo es una llamada al operador new colocación estándar (a través de una nueva expresión de ubicación), por lo que necesita incluir . Ese operador en particular es especial ya que no asigna memoria, solo devuelve lo que le pasó (en este caso es el parámetro &p ). El resultado neto de la expresión es que un objeto ha sido construido.

Si combina esta syntax con llamadas al destructor explícito, puede lograr un control completo sobre la vida útil de un objeto:

 // Let's assume storage_type is a type // that is appropriate for our purposes storage_type storage; std::string* p = new (&storage) std::string; // p now points to an std::string that resides in our storage // it was default constructed // *p can now be used like any other string *p = "foo"; // Needed to get around a quirk of the language using string_type = std::string; // We now explicitly destroy it: p->~string_type(); // Not possible: // p->~std::string(); // This did nothing to our storage however // We can even reuse it p = new (&storage) std::string("foo"); // Let's not forget to destroy our newest object p->~string_type(); 

Cuándo y dónde debe construir y destruir el miembro std::string (vamos a llamarlo s ) en su clase Value depende de su patrón de uso para s . En este ejemplo mínimo, nunca construyes (y por lo tanto lo destruyes) en los miembros especiales:

 struct Value { Value() {} Value(Value const&) = delete; Value& operator=(Value const&) = delete; Value(Value&&) = delete; Value& operator=(Value&&) = delete; ~Value() {} uint64_t lastUpdated; union { uint64_t ui; int64_t i; float f; bool b; std::string s; }; }; 

El siguiente es, por lo tanto, un uso válido de Value :

 Value v; new (&v.s) std::string("foo"); something_taking_a_string(vs); using string_type = std::string; vs~string_type(); 

Como habrás notado, deshabilité la copia y el movimiento del Value . La razón de esto es que no podemos copiar o mover el miembro activo apropiado de la unión sin saber cuál es el que está activo, si hay alguno.

    Intereting Posts