C++ Kurs

Laufzeit-Typinformationen

Die Themen:

Manchmal kann es hilfreich sein, Informationen über den Datentyp eines Objekts (oder auch einer einfachen Variablen) zur Laufzeit zu erhalten. Hierzu bietet C++ die Typinformation zur Laufzeit an (Runtime type information = RTTI). Wie und für was diese Typinformation eingesetzt werden kann, das lässt sich wieder einmal am besten anhand eines Beispiels erläutern.

RTTI

Das Ausgangsbeispiel

Nachfolgend sind wieder die bekannten Klasse GBase sowie die beiden von GBase abgeleiteten Klassen Circle und Bar definiert. In main() werden dann drei Grafikobjekte dynamisch erstellt und deren Zeiger in einem Feld vom Typ GBase* abgelegt.

PgmHeader// Klassendefinitionen
class GBase
{....};
class Circle: public GBase
{....};
class Bar: public GBase
{....};
// main() Funktion
int main()
{
    // Basiszeigerfeld definieren
    GBase *pGraphic[3];
    // 3 Grafikobjekte im Feld ablegen
    pGraphic[0] = new Bar(...);
    pGraphic[1] = new Circle(...);
    pGraphic[2] = new Bar(...);
    ....
    // Objekte hier wieder löschen
}

Soweit nicht Ungewöhnliches.

Erweitern wir jetzt die Basisklasse um eine Memberfunktion Align(...), die es uns ermöglichen soll, zwei Grafikobjekte auszurichten. Die Ausrichtung soll der Einfachheit halber in der Art erfolgen, dass die X-Positionen gleichgesetzt werden.

PgmHeadervoid GBase::Align(GBase *pGObj)
{
    pGObj->xPos = xPos; // xPos gleich setzen
}
// Mögliche Aufrufe
pGraphic[0]->Align(pGraphic[1]);
pGraphic[0]->Align(pGraphic[2]);

Unterhalb der Definition der Memberfunktionen sehen Sie zwei mögliche Aufrufe der neuen Memberfunktion. Hierbei werden die Grafikelemente 1 und 2 an dem Grafikelement 0 ausgerichtet.

Diese Memberfunktion Align(...) wollen wir gleich so abändern, dass nur noch gleiche Grafiken ausgerichtet werden können, d.h. es können zwei Bar oder zwei Circle Objekte zueinander ausgerichtet werden aber nicht mehr ein Bar Objekt an einem Circle Objekt. Und hier kommt die RTTI ins Spiel.

Die runtime type information (RTTI)

Um zur Laufzeit feststellen zu können, ob die auszurichtenden Objekte vom gleichen Typ sind, muss die Memberfunktion Align(...) Informationen über die Typen der auszurichtenden Objekte erhalten. Damit ein Objekt aber überhaupt eine Typinformation (RRTI) enthält, muss dessen Klasse polymorph sein, d.h. sie muss mindestens eine virtuelle Memberfunktion besitzen. Wie bereits im Kapitel über virtuelle Memberfunktionen erwähnt, ist eine Memberfunktion einer abgeleiteten Klasse automatisch virtuell, wenn die entsprechende Memberfunktion in der Basisklasse als virtuell deklariert ist. Wenn also für eine Klasse RTTI benötigt wird, muss entweder die Klasse selbst oder aber eine ihrer Basisklassen mindestens eine virtuelle Memberfunktion enthalten.

Aber sehen wir uns jetzt die RTTI an. Um die RTTI einsetzen zu können, muss die Datei <typeinfo> mittels include-Anweisung zunächst eingebunden werden. Diese Datei enthält die Definition der für die RTTI benötigten Struktur type_info.

Um die Typinformation eines Objekts letztendlich zu erhalten, ist der Operator typeid aufzurufen.

type_info& typeid(DTYP);

DTYP ist die Variable oder das Objekt, dessen Typinformation bestimmt werden soll. Für DTYP kann aber auch ein bestimmter Datentyp oder Klassenname eingesetzt werden. Als Ergebnis liefert der Operand eine Referenz auf die Struktur type_info, die die Typinformation enthält. Zur Auswertung dieser Struktur stehen verschiedene überladene Operatoren zur Verfügung, die in der nachfolgenden Tabelle aufgeführt sind.

Operator Bedeutung
bool operator == (const type_info& COp2) Vergleicht ob zwei Datentypen identisch sind
bool operator != (const type_info& COp2) Vergleicht ob zwei Datentypen unterschiedlich sind
bool before(const type_info& COp2) Funktion für interne Zwecke
const char* name() Liefert einen Zeiger auf den Datentyp als ASCII String; der String ist aber nicht standardisiert, sodass unterschiedliche Compiler unterschiedlich Strings liefern können.

Somit kann z.B. mit folgender Anweisung verglichen werden, ob das Objekt anyGraph zur Klasse Bar gehört:

if (typeid(anyGraph) == typeid(Bar))
    ....

Und damit lässt sich die Memberfunktion Align(...) der Basisklasse nun wie angegeben definieren.

PgmHeadervoid GBase::Align(GBase* pGObj)
{
    // Grafiktypen vergleichen
    if (typeid(*this) != typeid(*pGObj))
    {
        // Typen ungleich, Meldung ausgeben und Ausnahme auslösen
        cout << "Fehler beim Ausrichten " << typeid(*this).name();
        cout << '-' << typeid(*pGObj).name()<< endl;
        throw "Ungleiche Grafikelemente!";
    }
    // Gleiche Grafiktypen, dann ausrichten
    pGObj->xPos = xPos;
    pGObj->yPos = yPos;
}

Die Memberfunktion Align(...) erhält als Parameter einen Zeiger auf das auszurichtende Objekt. In der Memberfunktion wird zuerst der Datentyp des aktuellen Objekts typeid(*this) mit dem des auszurichtenden Objekts typeid(*pGObj) verglichen. Hierbei ist zu beachten, dass die Zeiger dereferenziert werden müssen, da ja die Datentypen der Objekte und nicht die der Zeiger ermittelt werden sollen. Die Datentypen der Zeiger ist hier immer GBase*! Sind die Datentypen unterschiedlich, so werden die Namen der beiden Datentypen ausgegeben (Aufruf der Memberfunktion name( ) ) und eine Ausnahme ausgelöst. Bei gleichen Datentypen werden die Objekte dann entsprechend der Vorgabe ausgerichtet.

Das Vergleichen von Datentypen funktioniert übrigens auch dann, wenn die Objekte über Klassen-Templates erstellt wurden.

Beispiel und ÜbungUnd hier geht's zum Beispiel und zur Übung.