Pregunta

Tengo un código que he usado con éxito durante algunos años para implementar un "objeto de tipo variante"; es decir, un objeto de C ++ que puede contener un archivo de valores de varios tipos, pero sólo los usos (aproximadamente) como la cantidad de memoria como el mayor de los posibles tipos. El código es similar en espíritu a una unión etiquetado, excepto que soporta tipos de datos no-POD también. Esto se logra mediante el uso de la magia de un char buffer, la colocación de nuevo / borrado, y reinterpret_cast <>.

Recientemente he intentado compilar este código bajo gcc 4.4.3 (con -O3 y -Wall), y tengo un montón de advertencias de esta manera:

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

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

He pegado un 'versión de juguete' de mi código de abajo; ¿hay algo que pueda hacer para que mi código para hacerlo más seguro bajo gcc 4.4.3, sin dejar de apoyar a los tipos de datos no-POD? Sé que como último recurso, siempre podría compilar el código con -fno-estricta-aliasing, pero sería bueno tener código que no se rompa bajo la optimización de modo que prefiero no hacerlo.

(Tenga en cuenta que me gustaría evitar la introducción de un impulso o dependencia C ++ 0X en el código base, por lo que mientras realce / C ++ soluciones 0X son interesantes, prefiero algo un poco más antigua)

#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;
}
¿Fue útil?

Solución

OK, puede hacerlo si está dispuesto para almacenar un * extra vacío. Cambié formato su muestra un poco, así que era más fácil para mí trabajar con. Mira esto y ver si se ajusta a sus necesidades. Además, nota que me proporcionó algunas muestras para que pueda añadir algunas plantillas a lo que ayudará a la facilidad de uso. Ellos pueden extenderse mucho más, pero que deben darle una buena idea.

También hay algunas cosas salida para ayudarle a ver lo que está pasando.

Una cosa más, supongo que sabe que necesita dar un adecuado copia-ctor y asignación operador pero que no es el quid de este problema.

Mi g ++ versión Info:

  

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

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

Otros consejos

Me hubiera escrito el código de este modo:

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

pero supongo que todo el mundo tiene su propio gusto.

Por cierto, el código está libre de errores, no se ha hecho cargo de los posibles problemas de alineación, no se puede simplemente poner un objeto en cualquier punto de la memoria, hay una restricción de respetar, que cambian con cada tipo. En C ++ 0x, no es la palabra clave alignof conseguirlo, y algunas otras utilidades para obtener almacenamiento alineados.

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

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

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;
      }
   }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top