2012-05-21 27 views
24

Estoy actualizando una estructura mía y quería agregarle un miembro std :: string. La estructura original están presentes:C++ 11 unión anónima con miembros no triviales

struct Value { 
    uint64_t lastUpdated; 

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

Simplemente añadiendo un miembro std :: string a la unión, por supuesto, provoca un error de compilación, ya que normalmente se necesita añadir los constructores no triviales del objeto. In the case of std::string (text from informit.com)

Since std::string defines all of the six special member functions, U will have an implicitly deleted default constructor, copy constructor, copy assignment operator, move constructor, move assignment operator and destructor. Effectively, this means that you can't create instances of U unless you define some, or all of the special member functions explicitly.

A continuación, el sitio web va a dar 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. Pregunté ## C++ en freenode y me dijeron que la forma correcta de hacerlo era poner el constructor de la estructura en su lugar 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 de ahí no puedo entender cómo haga 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 hacer que alguien me explique esto un poco más claro?

+2

Me parece que lo que _really_ necesita es una [variante] adecuada (http://www.boost.org/libs/variant/) ... – ildjarn

+1

Ya tengo una clase de variante que estoy usando en otra parte . No lo estoy usando en este caso, porque esto es para datos serializados en una red y quería mantener los datos pequeños, de modo que todo lo que una Variante mantiene interna (como escribir información) a la clase que quería mantener externa y decidir ¿Qué es lo que se basa en el esquema del paquete? – OmnipotentEntity

Respuesta

21

No es necesario colocarlo aquí.

miembros alternativas no serán inicializados por el constructor generado por el compilador, pero no debe haber ningún problema para elegir uno e inicializar utilizando el ctor-inicializador lista normales. Los miembros declarados dentro de uniones anónimas son en realidad miembros de la clase contenedora, y pueden ser inicializados en el constructor de la clase contenedora.

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

A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members. If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X .

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

A ctor-initializer may initialize a variant member of the constructor’s class. If a ctor-initializer specifies more than one mem-initializer for the same member or for the same base class, the ctor-initializer is ill-formed.

Así que 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 colocación de nuevo todavía debe usarse cuando un vivifying miembro de variante que no sea el otro inicializado en el constructor.

+0

¿Qué sucede si el constructor 'Foo' está definido pero no elige una de esas variantes de miembro (es decir,' Foo() {} 'en lugar de' Foo(): p() {} ')? GCC 5.1 y Clang 3.6 salen a compilar el constructor sin ninguna advertencia o error: http://melpon.org/wandbox/permlink/gLkD49UOrrGhFUJc Sin embargo, no está claro lo que dice la norma sobre ese caso. – dkim

+0

@dkim: estoy bastante seguro de que deja a todos los miembros de la variante en el mismo estado, específicamente el almacenamiento obtenido pero la inicialización no realizada. La Sección 3.8 (Vida útil del objeto) especifica las operaciones permitidas en los miembros en dicho estado. –

13

Ese new (&p) Point() ejemplo es una llamada al operador de colocación estándar new (a través de una nueva expresión de ubicación), de ahí que necesite 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 un objeto ha sido construido.

Si se combina esta sintaxis con llamadas destructor explícitas a continuación, se puede lograr un control completo sobre el tiempo de vida 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 donde se debe construir y destruir el miembro de std::string (vamos a llamarlo s) en su clase Value depende de su patrón de uso para s. En este mínimo ejemplo, nunca se construye (y por lo tanto destrucción) 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(v.s); 
using string_type = std::string; 
v.s.~string_type(); 

Como se habrán dado cuenta, he desactivado copiando y moviendo 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.

+5

* "// Necesario para evitar una peculiaridad del lenguaje" * - en realidad, esto es incorrecto - usted * puede * llamar al destructor directamente, si lo hace bien (necesita resolver el alcance correctamente): 'p-> std :: cadena :: ~ cadena(); '. ¿Más legible, sin embargo? Bueno, ciertamente se ve más complicado, pero usa un tipo de datos bien conocido, mientras que la solución anterior es más compacta (aparte de la línea de código adicional para 'usar'), pero presenta un alias raramente conocido. Sin duda, una cuestión de gusto personal (en lo que respecta a mí mismo, votaría por el tipo de datos conocidos ...). – Aconcagua

Cuestiones relacionadas