Dynamischer Datentyp / Variant

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

Variant-Datenstruktur während der Benutzung 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

  1. 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