6

「バリアント型オブジェクト」を実装するために数年間正常に使用しているコードがいくつかあります。つまり、さまざまなタイプの値を保持できるC ++オブジェクトですが、可能なタイプの中で最大のものと同じだけのメモリを(およそ)使用します。このコードは、POD以外のデータ型もサポートしていることを除けば、タグ付き共用体と精神的に似ています。これは、charバッファー、配置new / delete、およびreinterpret_cast<>を使用してこの魔法を実現します。

最近、gcc 4.4.3(-O3と-Wallを使用)でこのコードをコンパイルしようとしましたが、次のような警告がたくさん表示されました。

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

私が読んだことから、これはgccの新しいオプティマイザが「バギー」コードを生成する可能性があることを示しています。これは明らかに避けたいものです。

以下にコードの「おもちゃバージョン」を貼り付けました。非PODデータ型をサポートしながら、gcc 4.4.3でコードをより安全にするために、コードに対してできることはありますか?最後の手段として、いつでも-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

4 に答える 4

1

OK、余分なボイドを保存する場合はそれを行うことができます*。サンプルを少し再フォーマットしたので、作業が簡単になりました。これを見て、それがあなたのニーズに合っているかどうかを確認してください。また、使いやすさを向上させるためにいくつかのテンプレートを追加できるように、いくつかのサンプルを提供したことに注意してください。それらはもっと拡張することができますが、それはあなたに良い考えを与えるはずです。

何が起こっているのかを確認するのに役立つ出力もあります。

もう1つ、適切なcopy-ctorとassignment-operatorを提供する必要があることを知っていると思いますが、それはこの問題の核心ではありません。

私のg++バージョン情報:

g ++ --version g ++(SUSE Linux)4.5.020100604[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;
}
于 2010-11-24T06:22:28.000 に答える
1

私は次のようにコードを書いたでしょう:

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

でも、誰もが自分の好みを持っていると思います。

ところで、あなたのコードにはバグがあり、可能性のあるアラインメントの問題に対処していません。メモリ内の任意の場所にオブジェクトを配置することはできません。尊重すべき制約があり、これは型ごとに異なります。C++0x では、alignofそれを取得するためのキーワードと、アラインされたストレージを取得するための他のいくつかのユーティリティがあります。

于 2010-11-24T07:15:12.107 に答える
0

私は、GCC (4.2.4、run with ) を一時的に-Wstrict-aliasing=2使用して文句を言わないように説得することができました。void *

void SetValueDuck(const Duck & duck) {ChangeType(TYPE_DUCK); void *t=&_data; reinterpret_cast<Duck*>(t)[0] = duck;}
于 2010-11-23T23:43:37.430 に答える
0

これの必要性や使用法はまだ理解できませんが、g++ 4.4.3 with -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;
      }
   }
}
于 2010-11-24T00:25:53.573 に答える