Ăśber den praxisrelevanten Einsatz der Template-Metaprogrammierung
Seite 5: Exkurs: Boost.Variant versus QVariant
Exkurs: Boost.Variant versus QVariant
Boost.Variant gehört zu den sogenannten "discriminated union wrappers", auf gut Deutsch vielleicht "union
mit Unterscheidungsvermögen". Eine union
in C/C++ ist eine Struktur, bei der alle Datenfelder denselben Speicherbereich belegen (anders als bei einer struct
, bei der die Datenfelder hintereinander im Speicher liegen).
Die union
kann eine vorher (bei der Definition) festgelegte Auswahl an Typen enthalten, aber wegen des gemeinsamen Speicherbereichs zu jeder Zeit nur genau einen bestimmten:
union {
   int i;
   float f;
} u;
u.i = 12;Â Â Â // u now contains an int
u.f = 1.2f  // u now contains a float
int i = u.i; // ouch: u contains float,
            // but we extract int
Leider hat die C-union
einige gravierende Nachteile. Da wäre zuvorderst zu nennen, dass die union
nicht "weiß", was gerade in ihr gespeichert ist ("non-discriminated"). Das wäre kein so großes Problem, wenn der Standard nicht unter Androhung undefinierten Verhaltens vorschriebe, nur genau das Datenfeld zu lesen, das als Letztes geschrieben wurde. Dass das Entwickler in der Praxis ohne einen zusätzlichen "Diskriminator" (etwa eine enum
) nicht einhalten können, sollte nicht weiter überraschen.
Des Weiteren gibt es in C++ zahlreiche Einschränkungen bezüglich der in einer union
erlaubten Typen. Ohne weiter ins Detail zu gehen: Die meisten benutzerdefinierten Typen lassen sich nicht in einer union
platzieren.
Boost.Variant will genau ein solches enum
/union
-Paar sein, verpackt in ein einfach zu benutzendes Klassen-Template. Genauso effizient, aber ohne die Probleme der nackten C-union
. Zum Beispiel "weiß" Boost.Variant zu jeder Zeit, welchen Typ es gerade enthält, und wirft Ausnahmen, wenn auf den falschen zugegriffen wird. Sie kann fast alle Typen enthalten (auch benutzerdefinierte), aber wie die C-union
nur solche, die man bei der Definition (hier: Instanziierung) angegeben hat:
boost::variant<
  int,
  float,
  std::string
> v;
v = 12;Â Â // v now contains an int
v = 1.2f; // v now contains a float
std::string hi("Hi");
v = hi;Â Â // v now contains a string
         // throws exception:
int i = boost::get<int>( v );
QVariant, das besser QAny genannt worden wäre, hat außer dem Namen nicht viel mit Boost.Variant gemein, dafür umso mehr mit Boost.Any (und mit Einschränkungen CORBA::Any). Wie die Namen der verwandten Klassen aus anderen Bibliotheken andeuten, kann eine QVariant jeden beliebigen ("any") Datentyp enthalten (nicht nur solche, die man bei der Definition angegeben hat):
QVariant v;Â Â Â // v is empty
v = 12;Â Â Â Â Â Â Â // v contains an int
v = 1.2;Â Â Â Â Â Â // v contains a double
v = Qt::black; // v contains a color
QColor color = qvariant_cast<QColor>( v );
Da jedoch QVariant als Diskriminator (siehe oben) ein einfaches int
verwendet,
sind benutzerdefinierte Typen vorher zu registrieren (um ihnen einen Diskriminator-Wert zuzuordnen):
class MyClass { /*...*/ };
Q_DECLARE_METATYPE( MyClass ) // register 'MyClass'
Boost.Any verwendet std::type_info
als Diskriminator und kommt daher ohne explizite Registrierung aus). Danach lassen sie sich (fast) wie eingebaute Typen verwenden:
QVariant v;
// QVariant::operator= isn't a template...
v.setValue( MyClass() );
// alternative:
v = qVariantFromValue( MyClass() );
MyClass mc = qvariant_cast<MyClass>( v );
(ane)