Domanda

devo certo codice che ho usato con successo per alcuni anni per attuare una "variante-tipo di oggetto"; ovvero un C ++ oggetto che può contenere un valori di vari tipi, ma solo usi (circa) la quantità di memoria più grande dei possibili tipi. Il codice è simile nello spirito a un tag-union, tranne che supporta i tipi di dati non-POD pure. Si compie questa magia utilizzando un buffer char, nuova collocazione / eliminazione, e reinterpret_cast <>.

Recentemente ho provato la compilazione di questo codice sotto gcc 4.4.3 (con -O3 e -Wall), ed ha ottenuto un sacco di avvertimenti in questo modo:

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

Da quello che ho letto, questa è un'indicazione che il nuovo ottimizzatore del CCG potrebbe generare il codice 'buggy', che io, ovviamente, vorrebbe evitare.

Ho incollato un 'versione giocattolo' del mio codice qui sotto; c'è qualcosa che posso fare per il mio codice per renderlo più sicuro sotto gcc 4.4.3, pur sostenendo i tipi di dati non-POD? So che in ultima istanza potrei sempre compilare il codice con -fno-strict-aliasing, ma sarebbe bello avere il codice che non si rompe sotto l'ottimizzazione così preferisco non farlo.

(Si noti che vorrei evitare l'introduzione di una spinta o C ++ 0X dipendenza nel codice di base, così mentre le soluzioni / Boost C ++ 0x vengono interessante, io preferirei qualcosa di un po 'più vecchio stile)

#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;
}
È stato utile?

Soluzione

OK, si può fare se si è disposti a memorizzare un * in più vuoto. Ho riformattato il campione un po 'così è stato più facile per me lavorare con. Guardate questo e vedere se si adatta alle tue esigenze. Inoltre, si noti che ho fornito alcuni esempi in modo da poter aggiungere alcuni modelli ad esso che aiuterà l'usabilità. Essi possono essere estesi molto di più, ma che dovrebbe darvi una buona idea.

Ci sono anche alcune cose di uscita per aiutarvi a vedere cosa sta succedendo.

Ancora una cosa, suppongo che voi sapete che è necessario fornire adeguata copia ctor e assegnazione operatore, ma che non è il punto cruciale di questo problema.

Il mio g ++ informazioni sulla versione:

  

g ++ --version   g ++ (SUSE Linux) 4.5.0 20.100.604 [gcc-4_5-ramo di revisione 160.292]

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

Altri suggerimenti

I avrebbe scritto il codice in questo modo:

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

ma credo che ognuno ha avuto il suo proprio gusto.

Tra l'altro, il codice è bacato, non avete preso cura di eventuali problemi di allineamento, non si può semplicemente mettere un oggetto in qualsiasi punto nella memoria, c'è un vincolo di rispettare, che cambiano con ogni tipo. In C ++ 0x, v'è la parola chiave alignof per farlo, e un paio di altri programmi di utilità per ottenere stoccaggio allineato.

sono riuscito a convincere GCC (4.2.4, correre con -Wstrict-aliasing=2) non lamentarsi utilizzando un void * temporanea, vale a dire.

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

non riesco ancora a comprendere la necessità o l'uso per questo, ma g ++ 4.4.3 con -O3 -Wall funziona con la seguente patch. Se funziona, si può condividere il caso d'uso, perché avete bisogno di questo?

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;
      }
   }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top