dynamic_cast                                  Übersicht Keywords C++


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

D
er 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++