У меня есть код, который я успешно использовал в течение нескольких лет для реализации «объекта варианта типа»;то есть объект 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;
}