سؤال

لدي بعض التعليمات البرمجية التي كنت أستخدمها بنجاح لعدة سنوات لتنفيذ "كائن من النوع المتغير" ؛ وهذا يعني أن كائن C ++ يمكن أن يحمل قيمًا من أنواع مختلفة ، ولكنه يستخدم فقط (تقريبًا) بقدر ذاكرة أكبر الأنواع الممكنة. يشبه الرمز بروح النقابة الموسومة ، باستثناء أنه يدعم أنواع البيانات غير POD أيضًا. إنه ينجز هذا السحر باستخدام مخزن مؤقت لـ Char ، وضع New/Delete ، وإعادة Pret_cast <>.

لقد حاولت مؤخرًا تجميع هذا الرمز ضمن GCC 4.4.3 (مع -o3 و -wall) ، وحصلت على الكثير من التحذيرات مثل هذا:

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

من ما قرأته ، هذا مؤشر على أن مُحسِّن GCC الجديد قد يولد رمز "BUGGY" ، والذي من الواضح أنني أرغب في تجنبه.

لقد لصق "نسخة لعبة" من الكود أدناه ؛ هل هناك أي شيء يمكنني القيام به في الكود لجعله أكثر أمانًا بموجب GCC 4.4.3 ، بينما لا يزال يدعم أنواع البيانات غير POD؟ أعلم أنه بصفتي ملاذًا أخير ، يمكنني دائمًا تجميع الكود باستخدام -lias-alias-alias-alias ، لكن سيكون من الجيد أن يكون لديك رمز لا ينكسر تحت التحسين ، لذا أود ألا أفعل ذلك.

(لاحظ أنني أرغب في تجنب إدخال زيادة في التبعية أو 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;
}
هل كانت مفيدة؟

المحلول

حسنًا ، يمكنك القيام بذلك إذا كنت على استعداد لتخزين فراغ إضافي *. لقد قمت بإعادة تهيئة العينة قليلاً ، لذا كان من الأسهل بالنسبة لي العمل معه. انظر إلى هذا ومعرفة ما إذا كان يناسب احتياجاتك. لاحظ أيضًا أنني قدمت بعض العينات حتى تتمكن من إضافة بعض القوالب إليها التي ستساعد على الاستخدام. يمكن تمديدها أكثر من ذلك بكثير ، لكن هذا يجب أن يمنحك فكرة جيدة.

هناك أيضًا بعض الأشياء المخرجات لمساعدتك في معرفة ما يجري.

شيء آخر ، أفترض أنك تعلم أنك تحتاج إلى توفير نسخ مناسبة للنسخ والمواجهة ، لكن هذا ليس جوهر هذه المشكلة.

معلومات إصدار G ++ الخاصة بي:

G ++-Version G ++ (SUSE Linux) 4.5.0 20100604 [GCC-4_5-Branch Revision 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;
}

نصائح أخرى

كنت قد كتبت الرمز مثل ذلك:

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

لكني أعتقد أن الجميع حصلوا على ذوقه الخاص.

بالمناسبة ، الكود الخاص بك هو عربات التي تجرها الدواب ، ولم تهتم بمشكلات المحاذاة المحتملة ، لا يمكنك فقط وضع كائن في أي وقت من المرحلة من الذاكرة ، فهناك قيود على الاحترام ، والتي تتغير مع كل نوع. في C ++ 0x ، هناك alignof الكلمة الرئيسية للحصول عليها ، وبعض المرافق الأخرى للحصول على تخزين محاذاة.

لقد تمكنت من إقناع 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;}

ما زلت لا أستطيع فهم الحاجة أو الاستخدام لهذا ولكن 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;
      }
   }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top