Der dynamic_cast Operator konvertiert
Zeiger bzw. Referenzen zwischen abgeleiteten Klassen. Man verwendet
ihn, wenn ein Basisklassenzeiger in einen Zeiger einer abgeleiteten
Klasse umgewandelt wird. Das nennt man "Downcasting"
(Das Upcasting läuft dagegen implizit).
Der dynamic_cast Operator bietet
wichtige Funktionalitäten. Er funktioniert nur mit
Zeigern auf polymorphe Klassen, die zumindest eine virtuelle
Member-Funktion besitzen, und überprüft zur Laufzeit, ob die
angestrebte Umwandlung des Basisklassenzeigers überhaupt typsicher ist. Wird dies
nicht gewährleistet, erfolgt die Rückgabe eines NULL-Zeigers. Dieser Operator und
der typeid Operator liefern run-time type information (RTTI)
in C++.
Zunächst schauen wir uns ein Szenario an, das bei Argumenten in
Funktionen Upcasts oder Downcasts erfordert.
class
Vater {};
class Kind : public Vater {};
void f(Vater* base){}
void g(Kind* derived){}
int main()
{
Vater base;
Kind derived;
f(&base); //erlaubt! Keine Umwandlung
f(&derived); //erlaubt!
derived kann in base umgewandelt werden (impliziter Upcast)
g(&base); //nicht erlaubt! base wird nicht in
derived umgewandelt werden (Downcast)
g(&derived); //erlaubt!
Keine Umwandlung
}
Fehlermeldung:
invalid
conversion from `Vater*' to `Kind*'
Während der Upcast problemlos
implizit erfolgt, schlägt der Compiler beim Downcast Alarm! Hier kommt
die große Stunde des dynamic_cast Operators, denn nur
er prüft während der Laufzeit auf Typsicherheit.
Also wenden wir
es versuchsweise an:
g(dynamic_cast<Kind*>(&base));
Der Compiler
beschwert sich erneut:
cannot
dynamic_cast `&base' (of type `class Vater*') to type `class Kind*'
(source type is not polymorphic)
Also
benötigen wir eine polymorphe Klassenhierarchie mit einer virtuellen
member-Funktion in der Basisklasse:
#include <iostream>
#include <conio.h>
using namespace std;
class Vater
{
public:
virtual void
virt_function() { cout << "Vater sagt Hallo" << endl; };
};
class Kind : public Vater
{
public:
virtual void
virt_function() { cout << "Kind sagt Hallo" << endl;
};
};
void f(Vater*
base) {base->virt_function();}
void g(Kind* derived)
{
if(derived)
derived->virt_function();
else
cout << "kein typsicherer Downcast" << endl;
}
int main()
{
Vater base;
Kind derived;
f(&base); //erlaubt!
Keine Umwandlung.
f(&derived); //erlaubt! derived kann in base
umgewandelt werden (impliziter Upcast)
g(dynamic_cast<Kind*>(&base)); //???
g(&derived); //erlaubt! Keine Umwandlung.
getch();
}
Was
passiert? Es klappt nicht! Der Compiler macht es uns bereits klar:
[Warning] dynamic_cast of `Vater base' to `class Kind*' can never
succeed
Ausgabe:
Vater sagt Hallo
Kind sagt
Hallo
kein
typsicherer Downcast
Kind sagt
Hallo
Warum geht das
nicht? Es kann nicht
funktionieren, weil ein Vater nicht in sein Kind
umgewandelt werden kann. Dazu fehlen die nötigen Informationen. Wenn das Kind noch
eine weitere Member-Variable besitzt, weiß niemand, was der Vater
machen soll, der sich als Kind ausgeben soll,
aber nichts von der Member-Variablen weiß. Wendet man hier einen
static_cast an, dann
funktioniert er, aber das Verhalten ist undefiniert.
Jetzt schauen wir uns an, wie man den Operator sinnvoll einsetzen kann:
#include
<iostream>
#include
<conio.h>
using
namespace std;
class
Vater
{
public:
virtual void virt_function() { cout << "Vater sagt Hallo"
<< endl; };
};
class
Kind1 : public Vater
{
public:
Kind1(){age=5;};
virtual void virt_function() { cout << "Kind1 sagt Hallo, bin "
<< getAge() << " Jahre alt." << endl; };
private:
int age;
int getAge(){return age;};
};
class
Kind2 : public Vater
{
public:
Kind2(){age=3;};
virtual void virt_function() { cout << "Kind2 sagt Hallo, bin "
<< getAge() << " Jahre alt." << endl; };
private:
int age;
int getAge(){return age;};
};
void
f(Vater* base)
{base->virt_function();}
void
g(Kind1* derived)
{
if(derived)
derived->virt_function();
else
cout << "kein typsicherer Downcast" << endl;
}
void
g(Kind2* derived)
{
if(derived)
derived->virt_function();
else
cout << "kein typsicherer Downcast" << endl;
}
int
main()
{
Vater base;
Kind1 derived1;
Kind2 derived2;
Vater* pVater1 = &base;
Vater* pVater2 = &derived2;
f(pVater1); //erlaubt!
Keine Umwandlung.
f(pVater2); //erlaubt!
derived kann in base umgewandelt werden (impliziter Upcast)
g(dynamic_cast<Kind1*>(pVater2)); //???
g(dynamic_cast<Kind2*>(pVater2)); //???
getch();
}
Ausgabe:
Vater sagt
Hallo
Kind2 sagt Hallo, bin 3 Jahre alt.
kein typsicherer Downcast
Kind2 sagt Hallo, bin 3 Jahre alt.
oder bei
folgender Änderung:
int
main()
{
Vater base;
Kind1 derived1;
Kind2 derived2;
Vater* pVater1 = &base;
Vater* pVater2 = &derived1;
f(pVater1); //erlaubt!
Keine Umwandlung.
f(pVater2); //erlaubt!
derived kann in base umgewandelt werden (impliziter Upcast)
g(dynamic_cast<Kind1*>(pVater2)); //???
g(dynamic_cast<Kind2*>(pVater2)); //???
getch();
}
Ausgabe:
Vater sagt Hallo
Kind1 sagt
Hallo, bin 5 Jahre alt.
Kind1
sagt Hallo, bin 5 Jahre alt.
kein
typsicherer Downcast
Man
sieht an diesem Beispiel sehr gut die Wirkung. In Zeile 2 sorgt
die virtuelle Klassenhierarchie nach
dem impliziten Upcast automatisch
für die Funktion der richtigen
abgeleiteten Klasse.
Anders ist es bei der überladenen Funktion g(...), die bereits einen
Zeiger auf eine abgeleitete Klasse als Argument fordert. Hier prüft dynamic_cast, ob auch die richtige
Kindklasse mittels Zeiger auf die Elternklasse angeliefert wurde.
Übersicht Keywords C++