2010-11-23 12 views
6

Tengo un código que he estado utilizando con éxito durante algunos años para implementar un "objeto de tipo variante"; es decir, un objeto C++ que puede contener valores de varios tipos, pero solo usa (aproximadamente) tanta memoria como el mayor de los tipos posibles. El código es similar en espíritu a una unión etiquetada, excepto que también admite tipos de datos que no son POD. Realiza esta magia mediante el uso de un búfer char, ubicación nueva/eliminar y reinterpret_cast <>.Colocación nueva contra gcc 4.4.3 reglas de alias estrictas

Recientemente he probado la elaboración de este código bajo gcc 4.4.3 (con -O3 y -Wall), y tiene un montón de advertencias similares a estas:

warning: dereferencing type-punned pointer will break strict-aliasing rules 

Por lo que he leído, esto es una indicación que el nuevo optimizador de gcc podría generar código "defectuoso", que obviamente me gustaría evitar.

He pegado una 'versión de juguete' de mi código a continuación; ¿hay algo que pueda hacer con mi código para hacerlo más seguro en gcc 4.4.3, mientras sigo soportando tipos de datos que no sean POD? Sé que, como último recurso, siempre podría compilar el código con -fno-strict-aliasing, pero sería bueno tener un código que no se rompa en la optimización, así que preferiría no hacerlo.

(Tenga en cuenta que me gustaría evitar introducir un impulso o una dependencia de C++ 0X en la base de código, así que mientras las soluciones boost/C++ 0X son interesantes, preferiría algo un poco más anticuado)

#include <new> 

class Duck 
{ 
public: 
    Duck() : _speed(0.0f), _quacking(false) {/* empty */} 
    virtual ~Duck() {/* empty */} // virtual only to demonstrate that this may not be a POD type 

    float _speed; 
    bool _quacking; 
}; 

class Soup 
{ 
public: 
    Soup() : _size(0), _temperature(0.0f) {/* empty */} 
    virtual ~Soup() {/* empty */} // virtual only to demonstrate that this may not be a POD type 

    int _size; 
    float _temperature; 
}; 

enum { 
    TYPE_UNSET = 0, 
    TYPE_DUCK, 
    TYPE_SOUP 
}; 

/** Tagged-union style variant class, can hold either one Duck or one Soup, but not both at once. */ 
class DuckOrSoup 
{ 
public: 
    DuckOrSoup() : _type(TYPE_UNSET) {/* empty*/} 
    ~DuckOrSoup() {Unset();} 

    void Unset() {ChangeType(TYPE_UNSET);} 
    void SetValueDuck(const Duck & duck) {ChangeType(TYPE_DUCK); reinterpret_cast<Duck*>(_data)[0] = duck;} 
    void SetValueSoup(const Soup & soup) {ChangeType(TYPE_SOUP); reinterpret_cast<Soup*>(_data)[0] = soup;} 

private: 
    void ChangeType(int newType); 

    template <int S1, int S2> struct _maxx {enum {sz = (S1>S2)?S1:S2};}; 
    #define compile_time_max(a,b) (_maxx< (a), (b) >::sz) 
    enum {STORAGE_SIZE = compile_time_max(sizeof(Duck), sizeof(Soup))}; 

    char _data[STORAGE_SIZE]; 
    int _type; // a TYPE_* indicating what type of data we currently hold 
}; 

void DuckOrSoup :: ChangeType(int newType) 
{ 
    if (newType != _type) 
    { 
     switch(_type) 
     { 
     case TYPE_DUCK: (reinterpret_cast<Duck*>(_data))->~Duck(); break; 
     case TYPE_SOUP: (reinterpret_cast<Soup*>(_data))->~Soup(); break; 
     } 
     _type = newType; 
     switch(_type) 
     { 
     case TYPE_DUCK: (void) new (_data) Duck(); break; 
     case TYPE_SOUP: (void) new (_data) Soup(); break; 
     } 
    } 
} 

int main(int argc, char ** argv) 
{ 
    DuckOrSoup dos; 
    dos.SetValueDuck(Duck()); 
    dos.SetValueSoup(Soup()); 
    return 0; 
} 
+0

Ese es el código extraño ... –

+0

¿Ha enviado este código al equipo de GCC, tal vez como un informe de error? – curiousguy

Respuesta

1

Bien, puede hacerlo si está dispuesto a almacenar un vacío adicional *. Volví a formatear tu muestra un poco para que fuera más fácil para mí trabajar con ella. Mire esto y vea si se ajusta a sus necesidades. Además, tenga en cuenta que proporcioné algunas muestras para que pueda agregar algunas plantillas que ayudarán a la usabilidad. Se pueden extender mucho más, pero eso debería darte una buena idea.

También hay algunos elementos de salida para ayudarlo a ver qué está sucediendo.

Una cosa más, supongo que sabe que debe proporcionar el copiador y el operador de asignación adecuados, pero esa no es la clave de este problema.

Mi g ++ información de la versión:

g ++ --version g ++ (SUSE Linux) 4.5.0 20100604 [revisión gcc-4_5-rama 160292]

#include <new> 
#include <iostream> 

class Duck 
{ 
public: 
    Duck(float s = 0.0f, bool q = false) : _speed(s), _quacking(q) 
    { 
    std::cout << "Duck::Duck()" << std::endl; 
    } 
    virtual ~Duck() // virtual only to demonstrate that this may not be a POD type 
    { 
    std::cout << "Duck::~Duck()" << std::endl; 
    } 

    float _speed; 
    bool _quacking; 
}; 

class Soup 
{ 
public: 
    Soup(int s = 0, float t = 0.0f) : _size(s), _temperature(t) 
    { 
    std::cout << "Soup::Soup()" << std::endl; 
    } 
    virtual ~Soup() // virtual only to demonstrate that this may not be a POD type 
    { 
    std::cout << "Soup::~Soup()" << std::endl; 
    } 

    int _size; 
    float _temperature; 
}; 

enum TypeEnum { 
    TYPE_UNSET = 0, 
    TYPE_DUCK, 
    TYPE_SOUP 
}; 
template < class T > TypeEnum type_enum_for(); 
template < > TypeEnum type_enum_for<Duck>() { return TYPE_DUCK; } 
template < > TypeEnum type_enum_for<Soup>() { return TYPE_SOUP; } 

/** Tagged-union style variant class, can hold either one Duck or one Soup, but not both at once. */ 
class DuckOrSoup 
{ 
public: 
    DuckOrSoup() : _type(TYPE_UNSET), _data_ptr(_data) {/* empty*/} 
    ~DuckOrSoup() {Unset();} 

    void Unset() {ChangeType(TYPE_UNSET);} 
    void SetValueDuck(const Duck & duck) 
    { 
    ChangeType(TYPE_DUCK); 
    reinterpret_cast<Duck*>(_data_ptr)[0] = duck; 
    } 
    void SetValueSoup(const Soup & soup) 
    { 
    ChangeType(TYPE_SOUP); 
    reinterpret_cast<Soup*>(_data_ptr)[0] = soup; 
    } 

    template < class T > 
    void set(T const & t) 
    { 
    ChangeType(type_enum_for<T>()); 
    reinterpret_cast< T * >(_data_ptr)[0] = t; 
    } 

    template < class T > 
    T & get() 
    { 
    ChangeType(type_enum_for<T>()); 
    return reinterpret_cast< T * >(_data_ptr)[0]; 
    } 

    template < class T > 
    T const & get_const() 
    { 
    ChangeType(type_enum_for<T>()); 
    return reinterpret_cast< T const * >(_data_ptr)[0]; 
    } 

private: 
    void ChangeType(int newType); 

    template <int S1, int S2> struct _maxx {enum {sz = (S1>S2)?S1:S2};}; 
    #define compile_time_max(a,b) (_maxx< (a), (b) >::sz) 
    enum {STORAGE_SIZE = compile_time_max(sizeof(Duck), sizeof(Soup))}; 

    char _data[STORAGE_SIZE]; 
    int _type; // a TYPE_* indicating what type of data we currently hold 
    void * _data_ptr; 
}; 

void DuckOrSoup :: ChangeType(int newType) 
{ 
    if (newType != _type) 
    { 
     switch(_type) 
     { 
     case TYPE_DUCK: (reinterpret_cast<Duck*>(_data_ptr))->~Duck(); break; 
     case TYPE_SOUP: (reinterpret_cast<Soup*>(_data_ptr))->~Soup(); break; 
     } 
     _type = newType; 
     switch(_type) 
     { 
     case TYPE_DUCK: (void) new (_data) Duck(); break; 
     case TYPE_SOUP: (void) new (_data) Soup(); break; 
     } 
    } 
} 

int main(int argc, char ** argv) 
{ 
    Duck sample_duck; sample_duck._speed = 23.23; 
    Soup sample_soup; sample_soup._temperature = 98.6; 
    std::cout << "Just saw sample constructors" << std::endl; 
    { 
    DuckOrSoup dos; 
    std::cout << "Setting to Duck" << std::endl; 
    dos.SetValueDuck(sample_duck); 
    std::cout << "Setting to Soup" << std::endl; 
    dos.SetValueSoup(sample_soup); 
    std::cout << "Should see DuckOrSoup destruct which will dtor a Soup" 
     << std::endl; 
    } 
    { 
    std::cout << "Do it again with the templates" << std::endl; 
    DuckOrSoup dos; 
    std::cout << "Setting to Duck" << std::endl; 
    dos.set(sample_duck); 
    std::cout << "duck speed: " << dos.get_const<Duck>()._speed << std::endl; 
    std::cout << "Setting to Soup" << std::endl; 
    dos.set(sample_soup); 
    std::cout << "soup temp: " << dos.get_const<Soup>()._temperature << std::endl; 
    std::cout << "Should see DuckOrSoup destruct which will dtor a Soup" 
     << std::endl; 
    } 
    { 
    std::cout << "Do it again with only template get" << std::endl; 
    DuckOrSoup dos; 
    std::cout << "Setting to Duck" << std::endl; 
    dos.get<Duck>() = Duck(42.42); 
    std::cout << "duck speed: " << dos.get_const<Duck>()._speed << std::endl; 
    std::cout << "Setting to Soup" << std::endl; 
    dos.get<Soup>() = Soup(0, 32); 
    std::cout << "soup temp: " << dos.get_const<Soup>()._temperature << std::endl; 
    std::cout << "Should see DuckOrSoup destruct which will dtor a Soup" 
     << std::endl; 
    } 
    std::cout << "Get ready to see sample destructors" << std::endl; 
    return 0; 
} 
+0

Por cierto, puede deshacerse del ctor predeterminado al cambiar los tipos a través de una sobrecarga (y aún más con más plantillas). De esta manera, puede copiar directamente la construcción o construir con parámetros y guardar la tara predeterminada inicial del ctor al cambiar de tipos. –

+0

Esto funciona, aunque no está claro por qué debería tener que almacenar el puntero adicional vacío, cuando puedo recrear ese puntero vacío "sobre la marcha" según sea necesario. Parece que la única razón para almacenar el puntero de vacío como una variable miembro es burlar el generador de advertencia del compilador, lo cual no es una razón muy satisfactoria para incurrir en un elemento por cada elemento pena de tiempo de ejecución. –

0

he logrado convencer a GCC (4.2.4, correr con -Wstrict-aliasing=2) no quejarse mediante el uso de un void * temporal, es decir.

void SetValueDuck(const Duck & duck) {ChangeType(TYPE_DUCK); void *t=&_data; reinterpret_cast<Duck*>(t)[0] = duck;} 
+0

Sin suerte, gcc 4.4.3 todavía advierte con el cambio anterior (excepto que ahora advierte sobre un puntero anónimo rompiendo las estrictas reglas de aliasing) –

0

todavía no puede entender la necesidad o el uso para esto, pero g ++ 4.4.3 con -O3 -Wall trabaja con el siguiente parche. Si funciona, ¿puedes compartir el caso de uso, por qué lo necesitas?

class DuckOrSoup 
{ 
public: 
    DuckOrSoup() : _type(TYPE_UNSET) {_duck = NULL; _soup = NULL;/* empty*/} 
    ~DuckOrSoup() {Unset();} 

    void Unset() {ChangeType(TYPE_UNSET);} 
    void SetValueDuck(const Duck & duck) {ChangeType(TYPE_DUCK); _duck = new (&_data[0])Duck (duck); } 
    void SetValueSoup(const Soup & soup) { ChangeType(TYPE_SOUP); _soup = new (&_data[0])Soup (soup); } 

private: 
    void ChangeType(int newType); 

    template <int S1, int S2> struct _maxx {enum {sz = (S1>S2)?S1:S2};}; 
    #define compile_time_max(a,b) (_maxx< (a), (b) >::sz) 
    enum {STORAGE_SIZE = compile_time_max(sizeof(Duck), sizeof(Soup))}; 

    char _data[STORAGE_SIZE]; 
    int _type; // a TYPE_* indicating what type of data we currently hold 
    Duck* _duck; 
    Soup* _soup; 
}; 

void DuckOrSoup :: ChangeType(int newType) 
{ 
    if (newType != _type) 
    { 
     switch(_type) 
     { 
     case TYPE_DUCK: 
      _duck->~Duck(); 
      _duck = NULL; 
      break; 
     case TYPE_SOUP: 
      _soup->~Soup(); 
      _soup = NULL; 
      break; 
     } 
     _type = newType; 
     switch(_type) 
     { 
     case TYPE_DUCK: _duck = new (&_data[0]) Duck(); break; 
     case TYPE_SOUP: _soup = new (&_data[0]) Soup(); break; 
     } 
    } 
} 
+0

¿Por qué tengo que almacenar diferentes tipos de datos en un solo objeto? Por las mismas razones, otras personas usan boost :: variant ... excepto que no quiero que mi programa dependa de boost. –

+0

El código anterior se compila sin advertencias, pero llama a los constructores de objeto dos veces (una en ChangeType() y otra en SetValue *(). Además, agrega N campos de puntero extra a cada objeto DuckOrSoup, que usa más memoria (mucho más en mi código que no es de juguete, que admite más de dos tipos de datos posibles) –

1

me habría escribió el código como lo siguiente:

typedef boost::variant<Duck, Soup> DuckOrSoup; 

pero supongo que todos tienen su propio gusto.

Por cierto, su código tiene errores, no se ha ocupado de posibles problemas de alineación, no puede simplemente colocar un objeto en cualquier punto de la memoria, hay una restricción que respetar, que cambia con cada tipo. En C++ 0x, existe la palabra clave alignof para obtenerla, y algunas otras utilidades para alinear el almacenamiento.

Cuestiones relacionadas