Dynamische Datentypen, oft als Variant bezeichnet, können eine definierte Anzahl von verschiedenen Datentypen aufnehmen. Die dienen in der Regel als Behälter, zur Datentypumwandlung oder zur Serialisierung von Datentypen und Strukturen. Sie sind in einer Vielzahl von Programmiersprachen vertreten, vor allem in Skriptsprachen wie zum Beispiel JavaScript, PHP und LUA. Sie sind immer da zu finden, wo es weniger um Laufzeit als um Einfachheit geht. Einfach in der Verwendung aus dem Grund, dass der Behälter automatische Typumwandlungen und Manipulationsfunktionen für die enthaltenen Daten mitbringen kann.
Beispiel (Quellcode): variant.cpp
Download (Quellcode): variant.zip
Beschreibung
Der Behälter für den dynamischen Datentyp, besteht in den meisten Fällen aus einem Typindikator und den einzelnen Datentypen, die dieser aufnehmen kann. Beim Setzen eines Wertes für einen der enthaltenen Datentypen, wird der Typindikator mitgesetzt. Zum Auslesen wird anhand des Typindikators geprüft, ob eine Typumwandlung erforderlich ist. Sofern keine Typumwandlungen oder eine Bestimmte nicht erlaubt sind, sollte eine Art der Fehlerbehandlung einsetzen.
Im Idealfall muss beim Kopieren eines Behälters nur der aktuelle enthaltene Typ übertragen werden. Da die Behälter oft einige Datentypen fassen können, kann sich eine Referenzzählung oder eine automatische Speicherbereingung lohnen, damit lästiges und zeitraubendes Kopieren entfallen kann.
In systemnahen Sprachen, können für native Datentypen und -strukturen (POD) auch Verbundtypen benutzt werden, hiermit lässt sich der Speicherbedarf minimieren. Allerdings ist es hier wichtig, den Kopiervorgang des Behälters genau zu kennen, da sonst unerwünschte Nebeneffekte auftreten können.
Um die Laufzeit gering zu halten, empfiehlt es sich außerdem, möglichst wenige Datentypen zu vereinen. In, zum Beispiel, C++ lassen sich mit variadischen Templates schnell verschiedene Variantstrukturen erzeugen.
In einigen Implementationen, wird auch gerne der Typindikator für den unbenutzen Behälter (Nil/Null) mitbenutzt um eine dahinterliegende Programmlogik zu vereinfachen. So lassen sich, als Beispiel, unbenutzte Parameter in einer Liste für Funktionsaufrufe definieren.
Durch den Beispielcode
Der Beispielcode, enthält ein Klasse (C++), die einen Variant für eine Ganzzahl und eine Zeichenkette bereitstellt:
die Konstruktoren
CVariant() : m_nType(TYPE_NONE), m_nInteger(0) {} CVariant(int nInteger) : m_nType(TYPE_INTEGER), m_nInteger(nInteger) {} CVariant(const char* pString) : m_nType(TYPE_STRING), m_nInteger(0), m_sString(pString) {} CVariant(const std::string& sString) : m_nType(TYPE_STRING), m_nInteger(0), m_sString(sString) {}
22 23 24 25 26 27 28 29
Beim Erzeugen der Klasse, wird der übergebene Datentyp automatisch geladen.
Auslesen und automatische Konvertierung
operator int()
52
operator const std::string&()
63
Ist eine Ganzzahl geladen, kann sie als Zeichenkette und Ganzzahl zurückgegeben werden. Das Gleiche gilt auch für die Zeichenkette.
Rückgabe des aktiven Typs
int GetType() const
83
Gibt an welcher Datentype gerade benutzt wird.
Codeanmerkungen
Die Aufrufparameter des Programms werden als mögliche konvertierte Ganzzahlen und Zeichenketten zurückgegeben:
std::vector<CVariant> variants; for(int n=1; n<argc; n++) variants.push_back(argv[n]);
99 100 101
So ergibt
./variant test 1 2 3 variant 4 5 6
die Ausgabe
Variant test... 8 elements: 1. Integer 0, String test 2. Integer 1, String 1 3. Integer 2, String 2 4. Integer 3, String 3 5. Integer 0, String variant 6. Integer 4, String 4 7. Integer 5, String 5 8. Integer 6, String 6