Search code examples
c++gccplacement-newpointer-aliasingtype-punning

Placement-new vs gcc 4.4.3 strict-aliasing rules


I've got some code that I've been using successfully for some years to implement a "variant-type object"; that is, a C++ object that can hold a values of various types, but only uses (approximately) as much memory as the largest of the possible types. The code is similar in spirit to a tagged-union, except that it supports non-POD data types as well. It accomplishes this magic by using a char buffer, placement new/delete, and reinterpret_cast<>.

I recently tried compiling this code under gcc 4.4.3 (with -O3 and -Wall), and got lots of warnings like this:

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

From what I've read, this is an indication that the gcc's new optimizer might generate 'buggy' code, which I obviously would like to avoid.

I've pasted a 'toy version' of my code below; is there anything I can do to my code to make it safer under gcc 4.4.3, while still supporting non-POD data types? I know that as a last resort I could always compile the code with -fno-strict-aliasing, but it would be nice to have code that doesn't break under optimization so I'd rather not do that.

(Note that I'd like to avoid introducing a boost or C++0X dependency into the codebase, so while boost/C++0X solutions are interesting, I'd prefer something a little more old-fashioned)

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

Solution

  • OK, you can do it if you are willing to store an extra void *. I reformatted your sample a bit so it was easier for me to work with. Look at this and see if it fits your needs. Also, note that I provided a few samples so you can add some templates to it that will help usability. They can be extended much more, but that should give you a good idea.

    There is also some output stuff to help you see what is going on.

    One more thing, I assume you know that you need to provide appropriate copy-ctor and assignment-operator but that is not the crux of this problem.

    My g++ version info:

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