Новое размещение против gcc 4.4.3 правила строгого наложения имен - PullRequest
6 голосов
/ 24 ноября 2010

У меня есть код, который я успешно использовал в течение нескольких лет для реализации «объекта варианта типа»;то есть объект C ++, который может содержать значения различных типов, но использует только (приблизительно) столько памяти, сколько самый большой из возможных типов.Код по духу похож на tagged-union, за исключением того, что он также поддерживает типы данных не POD.Это завершает эту магию с помощью буфера символов, размещения new / delete и reinterpret_cast <>.

Я недавно пытался скомпилировать этот код в gcc 4.4.3 (с -O3 и -Wall), и получил многоПредупреждения наподобие этого:

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

Из того, что я прочитал, это свидетельствует о том, что новый оптимизатор gcc может генерировать «глючный» код, которого я, очевидно, хотел бы избежать.Я вставил «игрушечную версию» своего кода ниже;Могу ли я что-нибудь сделать с моим кодом, чтобы сделать его более безопасным в gcc 4.4.3, при этом поддерживая типы данных не POD?Я знаю, что в крайнем случае я всегда мог скомпилировать код с -fno-strict-aliasing, но было бы неплохо иметь код, который не ломается при оптимизации, поэтому я бы предпочел этого не делать.

(Обратите внимание, что я бы не хотел вводить в кодовую базу зависимость boost или C ++ 0X, поэтому, хотя решения boost / C ++ 0X интересны, я бы предпочел что-то более старомодное)

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

Ответы [ 4 ]

1 голос
/ 24 ноября 2010

Я бы написал код так:

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

но я думаю, что у каждого есть свой вкус.

Кстати, ваш код глючит, вы не взялиЕсли вы заботитесь о возможных проблемах выравнивания, вы не можете просто поместить объект в какую-либо точку памяти, есть ограничение, которое нужно учитывать, которое меняется с каждым типом.В C ++ 0x есть ключевое слово alignof для его получения и несколько других утилит для выравнивания хранилища.

1 голос
/ 24 ноября 2010

ОК, вы можете сделать это, если хотите сохранить лишнюю ячейку *.Я немного переформатировал ваш образец, чтобы мне было легче с ним работать.Посмотрите на это и посмотрите, подходит ли оно вам.Также обратите внимание, что я предоставил несколько примеров, чтобы вы могли добавить к ним несколько шаблонов, которые помогут удобству использования.Они могут быть расширены гораздо больше, но это должно дать вам хорошую идею.

Есть также некоторые выходные данные, которые помогут вам увидеть, что происходит.

Еще одна вещь, я полагаю, выЗнайте, что вам нужно предоставить соответствующий copy-ctor и assignment-operator, но это не суть этой проблемы.

Информация о моей версии g ++:

g ++ --version g ++ (SUSE Linux) 4.5.0 20100604 [версия gcc-4_5-ветви 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 голосов
/ 24 ноября 2010

Я все еще не могу понять необходимость или использование для этого, но g ++ 4.4.3 с -O3 -Wall работает со следующим патчем.Если это работает, вы можете поделиться примером использования, зачем вам это нужно?

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 голосов
/ 24 ноября 2010

Мне удалось убедить GCC (4.2.4, запустить с -Wstrict-aliasing=2) не жаловаться, используя временное void *, то есть.

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