Unterschied dynamic/static cast



  • Hallo,

    ich suche jetzt schon eine ganze Weile nach einem genauen
    Unterschied.
    Bei einem dynamic_cast ist ein sicheres heruntercasten in
    Vererbungshierarchie möglich. Ist der einzige Unterschied,
    dass der static_cast unsicher und schneller ist?

    In dem Buch von Scott Meyers wird darauf hingewiesen, dass
    z.B. bei einem static_cast von einem Objekt ein temporäres Basis-
    objekt erzeugt wird. Wo kann ich solche Informationen finden
    oder ist das Compilerabhängig? In "Die C++ Programmiersprache"
    konnte ich so etwas leider nicht finden. Auch in "C++ Primer"
    fand ich keine genaueren Angaben.

    Viele Grüße,
    gallagher



  • Wo kann ich solche Informationen finden
    oder ist das Compilerabhängig?

    FAQ!



  • dynamic_cast ist für downcasts in vererbungshierarchien gedacht und static_cast um zwischen unabhängigen datentypen zu konvertieren, sofern diese geeignete operatoren bereitstellen. in gewissen fällen kannst du den halt auch für vererbung nehmen



  • Danke fuer eure Antworten.

    @zuckerlie: Leider wird die Fragen nicht in dem FAQ beantwortet. Es gibt ein Thema "CASTS: dynamic_cast und static_cast ???" das mir leider so nicht weiter hilft.

    @Skym0sh0: Wird bei einem Cast von einem Objekt immer der entsprechende Konstruktor aufgerufen und ein temp. Objekt erzeugt?



  • #include <iostream>
    using namespace std;
    
    class Lebewesen
    {
        public:
            virtual ~Lebewesen() {}
            virtual void hallo() = 0;
    };
    
    class Mensch : public Lebewesen
    {
        public:
            virtual void hallo() { cout << "Hallo Mensch!"; }
    };
    
    class Student: public Mensch
    {
        public:
            virtual void hallo() { cout << "Hallo Student!"; }
    };
    
    void fkt(Lebewesen* pl) // Von welchem Typ ist das Objekt, auf das pl zeigt?
    {
    
        // Student* ps = pl;    // error: Zeiger/Referenzen dürfen in der Vererbungshierarchie nicht nach oben zeigen.
                                // Und wieso nicht? Jeder Student ist ein Mensch, aber nicht jeder Mensch ist ein Student.
                                // Die Deklaration 'Student* ps' besagt, dass sich hinter dem Zeiger ein 'Student' befinden muss,
                                // aber wer garantiert uns, dass sich hinter dem pl-Zeiger ein Student verbirgt? Niemand!
    
        // Student* ps = static_cast<Student*>(pl); // Nun sagen wir dem Compiler, wir sind uns sicher, dass ps nicht nach oben zeigen wird,
                                                    // aber wissen wir das wirklich? Falls ps doch nach oben zeigt... autsch!
    
        Student* ps = dynamic_cast<Student*>(pl);   // dynamic_cast gibt 0 zurück, falls ps nach oben zeigen würde.
    
        if(ps==NULL)
            cout << "nach oben zeigen ist nicht!";
        else
            ps->hallo();
    }
    
    int main()
    {
        Mensch m;
        Student s;
    
        fkt(&m); // autsch! Hinter dem pl-Zeiger verbirgt sich kein Student.
        fkt(&s); // ok
    }
    

    Fazit: Im Gegensatz zum static_cast bietet der dynamic_cast zusätzliche Typsicherheit, da er noch zusätzlich einen Typ-Check zur Laufzeit durchführt.

    Aber jetzt musst du noch wissen, dass du das im Grunde genommen gar nicht wissen musst. Was du wissen musst ist, dass dynamic_cast i. d. R. (in Unit-Tests u. U. sinnvoll) auf ein schlechtes Design hinweist, also ein schlechtes Design der Grund ist, dass du dynamic_cast brauchst. Genausowenig brauchst du const_cast, denn entweder willst du const oder du willst kein const, also überleg dir vorher was du willst und mach die Deklaration gleich richtig. Und beim reinterpret_cast, da landest du schnell bei einem undefined behaviour, wenn du nicht genau weißt, was du tust.
    Fazit: Eigentlich ist nur static_cast unser Freund.

    :xmas1:



  • Gugelmoser schrieb:

    Was du wissen musst ist, dass dynamic_cast auf ein schlechtes Design hinweist, also ein schlechtes Design der Grund ist, dass du dynamic_cast brauchst.

    Ergänze in dem Satz mal ein "in der Regel" oder "meistens" 😉
    Außerdem gibt es auch Fälle in denen ein const_cast sinnvoll ist. :xmas1:

    Aber tendenziell gebe ich dir absolut recht. In den meisten Fällen sollte man wirklich nochmal überlegen, ob es auch wirklich sauber ist...



  • const_cast braucht man meist nur in 2 Fällen:
    .)Eine API, die nicht auf const-correctness achtet, wird verwendet.
    .)Non-const Memberfunktionen über die const-Versionen implementieren, um Redundanz zu vermeiden.



  • XSpille schrieb:

    Ergänze in dem Satz mal ein "in der Regel" oder "meistens"

    Einverstanden :p

    XSpille schrieb:

    Außerdem gibt es auch Fälle in denen ein const_cast sinnvoll ist.

    Hast du noch ein Beispiel?



  • Gugelmoser schrieb:

    Hast du noch ein Beispiel?

    Wir nehmen an, die const-Version von baz() ist eine ziemlich lange Methode.

    struct foo
    {
        bar const& baz() const;
    
        bar& baz()
        {
            return const_cast<bar&>(static_cast<foo const*>(this)->baz());
        }
    };
    


  • Gugelmoser schrieb:

    XSpille schrieb:

    Außerdem gibt es auch Fälle in denen ein const_cast sinnvoll ist.

    Hast du noch ein Beispiel?

    Ich dachte wenn man beipielsweise eine große Menge von Daten auf einen bestimmten Filter einschränkt, dann ist es in manchen Fällen wahrscheinlich, das man diese Menge weiter einschränken möchte. Deswegen kann es Sinn machen vorherige Ergebnisse zu Cachen. Obwohl ich jetzt eine const-Funktion aufrufe, verändere ich intern eine Datenstruktur, die die letzten x-Anfrage-Ergebnisse enthält.

    Also bei Optimierungen, die intern ein Caching verwalten.



  • @XSpille: Nein, sowas ist definitiv ein Fall für das mutable Keyword. Eine als const deklarierte Variable zu ent-consten erzeugt undefiniertes Verhalten 😉





  • 314159265358979_ schrieb:

    @XSpille: Nein, sowas ist definitiv ein Fall für das mutable Keyword. Eine als const deklarierte Variable zu ent-consten erzeugt undefiniertes Verhalten 😉

    Stimmt... Mutable hab ich völlig vergessen...
    Aber wenn ich eine std::map als member in meinem const-Objekt habe, dann ist der Member doch nicht automatisch auch const, oder?
    Die map selbst ist doch nicht-const. Also sollte es doch nicht undefiniert sein, oder?



  • XSpille schrieb:

    Aber wenn ich eine std::map als member in meinem const-Objekt habe, dann ist der Member doch nicht automatisch auch const, oder?

    Doch. Wenn *this const ist, sind auch alle Member const. (Es sei denn, sie sind als mutable deklariert)



  • 314159265358979_ schrieb:

    Gugelmoser schrieb:

    Hast du noch ein Beispiel?

    Wir nehmen an, die const-Version von baz() ist eine ziemlich lange Methode.

    struct foo
    {
        bar const& baz() const;
    
        bar& baz()
        {
            return const_cast<bar&>(static_cast<foo const*>(this)->baz());
        }
    };
    

    Andersherum kommst du mit einem const_cast aus:

    struct foo
    {
       foo& bar()
       {
          // viel Code
       }
    
       const foo& bar() const
       {
          return const_cast<foo*>( this )->bar();
       }
    }
    


  • DocShoe schrieb:

    314159265358979_ schrieb:

    Gugelmoser schrieb:

    Hast du noch ein Beispiel?

    Wir nehmen an, die const-Version von baz() ist eine ziemlich lange Methode.

    struct foo
    {
        bar const& baz() const;
    
        bar& baz()
        {
            return const_cast<bar&>(static_cast<foo const*>(this)->baz());
        }
    };
    

    Andersherum kommst du mit einem const_cast aus:

    struct foo
    {
       foo& bar()
       {
          // viel Code
       }
    
       const foo& bar() const
       {
          return const_cast<foo*>( this )->bar();
       }
    }
    

    Das wird nix. Kannst du garantieren, das die nicht-const Version von baz sich const verhält? Sonst darfst du sie ja nicht aus der const Version von baz aus aufrufen! Im Allgemeinen sollte das nicht der der sein, denn sonst bräuchtest du ja keine nicht-const Version :xmas1:



  • Ok, so langsam habe ich es. Es vielen Dank für die Antworten.

    Viele Grüsse,
    gallagher



  • bmario_ schrieb:

    Das wird nix. Kannst du garantieren, das die nicht-const Version von baz sich const verhält? Sonst darfst du sie ja nicht aus der const Version von baz aus aufrufen! Im Allgemeinen sollte das nicht der der sein, denn sonst bräuchtest du ja keine nicht-const Version :xmas1:

    😕



  • DocShoe schrieb:

    bmario_ schrieb:

    Das wird nix. Kannst du garantieren, das die nicht-const Version von baz sich const verhält? Sonst darfst du sie ja nicht aus der const Version von baz aus aufrufen! Im Allgemeinen sollte das nicht der der sein, denn sonst bräuchtest du ja keine nicht-const Version :xmas1:

    😕

    Ähm ja, war wohl zu kurz gefasst.

    Das man in einem nicht-const Objekt jede beliebige const Funktion aufrufen kann, sollte klar sein. Genauso klar ist es, dass du ohne Probleme aus nicht-const Funktionen const Funktionen aufrufen kannst und beim Rückgabewert etwaige const-ness wegcasten kannst. Das kannst du machen, da dein Objekt ohne nicht const ist und ein Benutzer deiner Klasse damit auch nicht erwartet, dass sich das Objekt const verhält.

    Anders herum ist es aber ein Problem. Hast du ein Objekt, dass const ist und du versuchst nun eine nicht const-Funktion zu benutzen (dank const cast auf this ohne Probleme möglich) hast du ein Problem, wenn diese nicht const Funktion etwas am Objekt verändert. Das ist ein Problem, da sich in diesem Fall aus Benutzersicht ein const Objekt, für das eine const Funktion aufgerufen wurde, ändert!

    Da du das Objekt selbst "ent-const-est" kannst du auch keinerlei Garantien darüber abgeben, ob die aufgerufene Funktion sich const verhält. Aber andersherum ist das egal. Du hast ein nicht-const Objekt und darfst damit alles machen was du willst, unter anderem Funktionen nutzen, die const sind.

    Hoffe das ist jetzt verständlicher, ansonsten mal bei Scott Meyers nachschlagen, der Mann kann definitiv besser erklären als ich.


Anmelden zum Antworten