Die Boost C++ Bibliotheken

Kapitel 24. Boost.Variant

Boost.Variant bietet mit boost::variant einen Typ an, der union ähnelt. So können in einer boost::variant-Variablen Werte unterschiedlicher Typen gespeichert werden. Zu jedem Zeitpunkt kann lediglich ein Wert gespeichert sein. Wird einer boost::variant-Variablen ein neuer Wert zugewiesen, wird der alte überschrieben. Der neue Wert darf jedoch einen anderen Typ als der alte Wert haben. Einzige Voraussetzung ist, dass die verwendeten Typen als Template-Parameter an boost::variant übergeben wurden und der entsprechenden boost::variant-Variablen bekannt sind.

boost::variant unterstützt beliebige Typen. So kann zum Beispiel ein std::string in einer boost::variant-Variablen gespeichert werden – etwas, was mit union vor C++11 nicht möglich war. Mit C++11 wurden die Regeln für union aufgeweicht, so dass ein std::string Bestandteil einer union sein kann. Da die Initialisierung der std::string-Variablen in einer union ein placement new erfordert sowie der Destruktor explizit aufgerufen werden muss, kann der Einsatz von boost::variant auch in einer Entwicklungsumgebung, die C++11 unterstützt, sinnvoll sein.

Beispiel 24.1. boost::variant in Aktion
#include <boost/variant.hpp>
#include <string>

int main()
{
  boost::variant<double, char, std::string> v;
  v = 3.14;
  v = 'A';
  v = "Boost";
}

Boost.Variant stellt eine Klasse boost::variant zur Verfügung, die in der Headerdatei boost/variant.hpp definiert ist. Da es sich bei boost::variant um ein Template handelt, muss mindestens ein Template-Parameter angegeben werden. Der oder die Template-Parameter beschreiben, welche Typen unterstützt werden. Für Beispiel 24.1 bedeutet dies, dass in der Variablen v ein double, ein char oder ein std::string gespeichert werden kann. Würden Sie versuchen, der Variablen v eine Zahl vom Typ int zuzuweisen, würde der Code nicht kompilieren.

Beispiel 24.2. Zugriff auf boost::variant mit boost::get()
#include <boost/variant.hpp>
#include <string>
#include <iostream>

int main()
{
  boost::variant<double, char, std::string> v;
  v = 3.14;
  std::cout << boost::get<double>(v) << '\n';
  v = 'A';
  std::cout << boost::get<char>(v) << '\n';
  v = "Boost";
  std::cout << boost::get<std::string>(v) << '\n';
}

Um die in der Variablen v gespeicherten Werte auszugeben, greifen Sie wie im Beispiel 24.2 auf eine freistehende Funktion boost::get() zu.

boost::get() erwartet als Template-Parameter einen der Typen, die für die entsprechende Variable erlaubt sind. Geben Sie einen ungültigen Typ an, führt dies zu einem Laufzeitfehler. Es findet keine Überprüfung der Typen zur Kompilierung statt.

Wenn Sie lediglich Werte auf einen Stream wie die Standardausgabe ausgeben möchten, können Sie die Gefahr falscher Typen insofern umgehen, als dass Sie Variablen vom Typ boost::variant direkt in einen Stream schreiben können. Sehen Sie sich dazu Beispiel 24.3 an.

Beispiel 24.3. Direkte Ausgabe von boost::variant auf einen Stream
#include <boost/variant.hpp>
#include <string>
#include <iostream>

int main()
{
  boost::variant<double, char, std::string> v;
  v = 3.14;
  std::cout << v << '\n';
  v = 'A';
  std::cout << v << '\n';
  v = "Boost";
  std::cout << v << '\n';
}

Für einen typsicheren Zugriff auf in einer boost::variant-Variablen gespeicherte Werte können Sie die Funktion boost::apply_visitor() verwenden.

Beispiel 24.4. Einen Besucher für boost::variant erstellen
#include <boost/variant.hpp>
#include <string>
#include <iostream>

struct output : public boost::static_visitor<>
{
  void operator()(double d) const { std::cout << d << '\n'; }
  void operator()(char c) const { std::cout << c << '\n'; }
  void operator()(std::string s) const { std::cout << s << '\n'; }
};

int main()
{
  boost::variant<double, char, std::string> v;
  v = 3.14;
  boost::apply_visitor(output{}, v);
  v = 'A';
  boost::apply_visitor(output{}, v);
  v = "Boost";
  boost::apply_visitor(output{}, v);
}

boost::apply_visitor() wird als erster Parameter ein Objekt vom Typ einer Klasse übergeben, die von boost::static_visitor abgeleitet sein muss. Die Klasse muss für jeden Typ der boost::variant-Variablen, für die sie verwendet werden soll, den Operator operator() überladen. Im Beispiel 24.4 muss dieser Operator entsprechend dreimal überladen sein, weil v die Typen double, char und std::string unterstützt.

boost::static_visitor ist ein Template. Der Typ des Rückgabewerts der überladenen Operatoren muss als Template-Parameter angegeben werden. Besitzen wie im Beispiel 24.4 die Methoden keinen Rückgabewert, wird kein Template-Parameter angegeben.

Der zweite Parameter, der an boost::apply_visitor() übergeben wird, ist eine Variable vom Typ boost::variant.

Wird boost::apply_visitor() verwendet, wird automatisch der überladene Operator operator() aufgerufen, der zum Typ des momentan in der boost::variant-Variablen gespeicherten Werts passt. So wird im Beispiel 24.4 bei jedem Aufruf von boost::apply_visitor() ein anderer überladener Operator aufgerufen – zuerst der für double, dann der für char und abschließend der für std::string.

Der Vorteil von boost::apply_visitor() ist, dass nicht nur automatisch der richtige Operator aufgerufen wird und der entsprechende Wert aus der boost::variant-Variablen als Parameter übergeben wird. boost::apply_visitor() stellt sicher, dass für alle von einer boost::variant-Variablen unterstützten Typen Operatoren überladen wurden. Würde im Beispiel 24.4 eine der drei Methoden fehlen, könnte der Code nicht kompiliert werden.

Wenn wie im vorherigen Beispiel mehrere überladene Operatoren das Gleiche tun, kann wie im Beispiel 24.5 der Code mit Hilfe eines Templates vereinfacht werden.

Beispiel 24.5. Ein Besucher mit Template-Funktion für boost::variant
#include <boost/variant.hpp>
#include <string>
#include <iostream>

struct output : public boost::static_visitor<>
{
  template <typename T>
  void operator()(T t) const { std::cout << t << '\n'; }
};

int main()
{
  boost::variant<double, char, std::string> v;
  v = 3.14;
  boost::apply_visitor(output{}, v);
  v = 'A';
  boost::apply_visitor(output{}, v);
  v = "Boost";
  boost::apply_visitor(output{}, v);
}

Da bei boost::apply_visitor() zur Kompilierung sichergestellt wird, dass der Code korrekt ist, sollte diese Funktion boost::get() vorgezogen werden.