J'ai du code que j'utilise avec succès depuis quelques années pour implémenter un "objet de type variant"; c'est-à-dire, un objet C++ qui peut contenir des valeurs de différents types, mais utilise seulement (approximativement) autant de mémoire que le plus grand des types possibles. Le code est similaire dans l'esprit à une union étiquetée, sauf qu'il prend également en charge les types de données non-POD. Il accomplit cette magie en utilisant un tampon de caractère, placement new/delete, et reinterpret_cast <>.Placement-nouveau vs gcc 4.4.3 règles d'aliasing strict
J'ai récemment essayé de compiler ce code sous gcc 4.4.3 (avec et -O3 -Wall), et a obtenu beaucoup d'avertissements comme celui-ci:
warning: dereferencing type-punned pointer will break strict-aliasing rules
D'après ce que j'ai lu, cela est une indication que le nouvel optimiseur de la gcc pourrait générer du code «bogué», ce que je souhaite évidemment éviter.
J'ai collé une «version jouet» de mon code ci-dessous; Est-ce que je peux faire quelque chose à mon code pour le rendre plus sûr sous gcc 4.4.3, tout en prenant en charge les types de données non-POD? Je sais qu'en dernier recours je pourrais toujours compiler le code avec -fno-strict-aliasing, mais ce serait bien d'avoir du code qui ne se casse pas sous l'optimisation, donc je préfère ne pas le faire. (Notez que je voudrais éviter d'introduire une amélioration ou une dépendance C++ 0X dans la base de code, alors même si les solutions boost/C++ 0X sont intéressantes, je préférerais quelque chose d'un peu plus démodé)
#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;
}
C'est le code capricieuse ... –
Avez-vous soumis ce code à l'équipe GCC, peut-être comme un rapport de bogue? – curiousguy