palabras conjuntos c++ constructor c++11 anonymous unions

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



union de conjuntos c++ (2)

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; }; };

Por supuesto, solo agregar un miembro std :: string a la unión provoca un error de compilación, porque normalmente se necesitaría agregar los constructores no triviales del objeto. En el caso de std :: string (texto de informit.com)

Dado que 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, operador de asignación de movimiento y destructor eliminados implícitamente. Efectivamente, esto significa que no puede crear instancias de U a menos que defina explícitamente algunas o todas las funciones especiales del miembro.

Luego el sitio web continúa dando el siguiente código de ejemplo:

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 al constructor en la estructura y me dio este código de ejemplo:

#include <new> 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 el resto de las funciones especiales que std :: string necesita definir, 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?


Ese new (&p) Point() ejemplo es una llamada al new operador de ubicación estándar (a través de una nueva expresión de ubicación), por lo que debe incluir <new> . Ese operador en particular es especial porque 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 se ha construido un objeto.

Si combina esta sintaxis con llamadas de destructor explícitas, 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();

Cuando y donde debe construir y destruir el miembro std::string (llamémoslo s ) en su clase de Value depende de su patrón de uso para s . En este ejemplo mínimo nunca lo construyes (y por lo tanto 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 un uso válido de Value :

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

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


No hay necesidad de colocación nueva aquí.

Los miembros de la variante no serán inicializados por el constructor generado por el compilador, pero no debería haber problemas para seleccionar uno e inicializarlo usando la lista normal de inicializador ctor . 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 similar a una unión es una unión o una clase que tiene una unión anónima como miembro directo. Una clase X similar a una unión 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 ctor puede inicializar un miembro variante de la clase del constructor. Si un inicializador ctor especifica más de un inicializador de mem para el mismo miembro o para la misma clase base, el inicializador ctor está mal formado.

Entonces el código puede ser simplemente:

#include <new> 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 ubicación nueva todavía debe usarse cuando se vivifica un miembro variante distinto al otro inicializado en el constructor.