cast operator, = und Konstruktor



  • Hallo,

    ich habe folgendes kleines Beispiel:

    class STRING
    {
    	char	*cp;
    
    	public:
    	STRING();
    	STRING( const STRING &src );
    	STRING( const char * );
    
    	STRING &operator = ( const STRING &src );
    	STRING &operator = ( const char * );
    };
    
    class DynamicVar
    {
    	public:
    	operator STRING () const;
    	operator const char * () const;
    };
    
    int main( void )
    {
    	STRING		value;
    	DynamicVar	dummy;
    
    	value = ((STRING)dummy);
    
    	return 0;
    }
    

    Beim Übersetzen mit dem alten Compilern von Borland (BC++ 5 und C++ Builder 5) habe ich keine Probleme. Jedoch beschwert sich Gnu C++, daß er in Zeile 26 nicht weiß, welchen Konstuktor der Klasse STRING er aufrufen soll. Das will ich nicht wirklich einsehen. Ist das ein Fehler in Gnu C++ oder ist mein Verständniss veraltet?

    mfg Martin



  • Ich kenne die Antwort nicht, möchte trotzdem den Code kommentieren:

    Lass es einfach sein, mit den vielen Konvertierungsoperatoren. Je mehr implizite Konvertierungen es gibt, desto schwieriger wird es, nachzuvollziehen, was wo wie konvertiert werden kann und wo da Mehrdeutigkeiten enstehen. operator const char*() ist schon recht böse.

    ALLCAPS-Namen werden typischerweise nur für Makros verwendet.

    Vermeide C-style casts. Verwende C++-style Casts, die genau das tun, was Du willst. Das lässt sich dann auch entsprechend ausdrücken, zB static_cast<STRING>(dummy).



  • mgaeckler schrieb:

    Jedoch beschwert sich Gnu C++, daß er in Zeile 26 nicht weiß, welchen Konstuktor der Klasse STRING er aufrufen soll. Das will ich nicht wirklich einsehen.

    Er hat zum Einen die Möglichkeit, den operator STRING von Dummy aufzurufen und den STRING per copy-Ctor damit initialisieren. Andererseits kann er den operator char const* von DUMMY aufrufen, und damit den STRING durch den entsprechenden Ctor initialisieren. Beide Varianten sind gleichwertig, der gcc hat also völlig recht.
    Wie kk schon sagte, lass es sein mit den impliziten Konvertierungen. Es spart zwar an ein zwei Stellen etwas schreibarbeit, weil du die Konvertierung nicht explizit hinschreiben musst, dafür kommen dann solche Fehler wie hier zu Tage, oder Code, der eigentlich nicht funktionieren dürfte, funktioniert auf sinnlose Weise, weil der Compiler hinter den Kulissen lustig die verrücktesten Konvertierungen anstellt. Beispiel:

    value = dummy + 5;
    

    Funktioniert mit deinen Konvertierungen wunderbar. Macht aber sicherlich keinen Sinn.



  • Hallo,

    erstmal Danke für Eure Hinweise. Der operator in Zeile 17 hat mir von Anfang an nicht geschmeckt. Ich wollte aber irgendwie ohne explizite Typumwandlung auskommen. Die Lösung ist natürlich einfach: Den unnötigen operator zu entfernen und die Variable vom Typ DynamicVar explizit nach const char * zu casten.

    Mich hätte halt interessiert, was genau der Standard verlangt. Ist BC++ zu gnädig oder Gnu C++ zu pedantisch?

    mfg Martin



  • krümelkacker schrieb:

    ALLCAPS-Namen werden typischerweise nur für Makros verwendet.

    Klar, das hat hier halt historische Gründe.

    krümelkacker schrieb:

    Vermeide C-style casts. Verwende C++-style Casts, die genau das tun, was Du willst. Das lässt sich dann auch entsprechend ausdrücken, zB static_cast<STRING>(dummy).

    Dazu fällt mir eine Frage ein: Was ist der Unterschied zw.

    variable1 = (Typ)variable2;
    

    und

    variable1 = static_cast<Typ>(variable2);
    

    mfg Martin



  • mgaeckler schrieb:

    Dazu fällt mir eine Frage ein: Was ist der Unterschied zw.

    variable1 = (Typ)variable2;
    

    und

    variable1 = static_cast<Typ>(variable2);
    

    mfg Martin

    Die erste Variante ist ein C-Cast, die zweite Variante ist ein neuerer C++-Cast, diese sollten den alten C-Casts auch immer vorgezogen werden.
    Sie sind ersten übersichtlicher als die C-Casts und zweitens auch in gewisser Weise sicherer, denn mit einem static_cast, kannste zum Beispiel nich das gleiche wie mit nem reinterpret_cast oder einem const_cast machen. Bei den C-Casts, fällt allerdings alles unter einen Hut.

    Lg freeG



  • mgaeckler schrieb:

    Dazu fällt mir eine Frage ein: Was ist der Unterschied zw.

    variable1 = (Typ)variable2;
    

    und

    variable1 = static_cast<Typ>(variable2);
    

    mfg Martin

    (Typ)var;
    // kann heißen:
    static_cast<Typ>(var);
    // oder
    reinterpret_cast<Typ>(var);
    // oder
    const_cast<Typ>(var);
    // oder
    const_cast<Typ>(reinterpret_cast<Typ const>(var));
    // oder
    ...
    

    Der C-Style-Cast kann also alle möglichen und möglicherweise auch ungewollten Casts ausführen. Beispielsweise castet er problemlos die Constness weg, ohne dass du das auf den ersten oder zweiten Blick erkennst.

    Die C++-Casts machen genau das, was du beabsichtigst. Der Compiler gibt einen Fehler aus, wenn der jeweilige Cast nicht möglich ist (beispielsweise weil die Constness weggecastet werden würde), so dass du dann entweder (wie in den meisten Fällen) den Fehler beheben oder auf einen stärkeren Cast umsteigen kannst.



  • fr33g schrieb:

    Bei den C-Casts, fällt allerdings alles unter einen Hut.

    Ja, allerdings nur mit bestimmten Einschränkungen.

    Das Paradebeispiel ist:

    struct A { };
    struct B : A { };
    struct C : A { };
    struct D : B, C { };
    
    int main () {
      D d;
      A* a1 = reinterpret_cast<A*>(&d); //erlaubt
      A* a2 = (A*)(&d); //ill-formed
    }
    

    Auch daher sollte man auf die Cast-Notation verzichten, außer natürlich man steht darauf, die genauen Regeln immer im Kopf zu haben.



  • mgaeckler schrieb:

    Dazu fällt mir eine Frage ein: Was ist der Unterschied zw.

    variable1 = (Typ)variable2;
    

    und

    variable1 = static_cast<Typ>(variable2);
    

    Das zweite ist ein static_cast.

    Das erste ist entweder ein static_cast, ein const_cast, ein reinterpret_cast oder ein Mix aus diesen dingen, je nachdem, was Typ und variable2 sind.

    Die C++-style Casts sind relativ speziell. Durch eine Verwending dieser kannst Du genau dokumentieren, was Du eigentlich bezweckst, wohingegen man beim C-style cast erstmal nachgucken muss, was da für Typen im Spiel sind. Es betrifft auch nicht nur die Lesbarkeit. Wenn Du dem Compiler über einen C++-style Cast genau sagst, was Du willst, kann er auch überprüfen, ob das auch wirklich so geht, wie Du es wolltest, statt alles "irgendwie" zu konvertieren.

    Ein static_cast ist das Gegenstück zu einer impliziten Konvertierung, mit dem Du u.a. auch einige Konvertierungen rückgängig machen kannst:

    double x = 3;                      // implizite Konvertierung int->double
    double y = static_cast<double>(3); // explizite Konvertierung int->double
    
    double* p = &x;
    void* q = p;                         // implizite Konvertierung double* -> void*
    double* r = static_cast<double*>(q); // explizite Konvertierung void* -> double*
    

    In der letzten Zeile ist der static_cast notwendig, da C++ aus Gründen der Typsicherheit so eine implizite Konvertierung -- im Gegensatz zu C -- nicht zulässt. "static" bedeutet hier, dass zur Laufzeit keinerlei Typprüfung stattfindet und der Compiler Dir im Falle von Zeigern und Referenzen blind vertraut. Mit einem static_cast lassen sich aber auch nur "verwandte" Typen ineinander konvertieren. Im Falle von Zeigern muss einer void* oder const void* sein oder es muss eine Art Vererbungshierachie vorhanden sein, also base* <-> derived*.

    Wildere Sachen kann man mit dem reinterpret_cast anstellen. Damit kann man auch Zeigertypen und Ganzzahltypen ineinander konvertieren die so gut wie nichts miteinander zu tun haben. Man sollte hier aber genau wissen, was man tut. Die Gefahr, undefiniertes Verhalten hervorzurufen, ist groß.

    Der const_cast ist eine Art Ausstiegsluke bzgl const. Mit ihm kannst Du bei Zeigern und Referenzen das const "wegcasten". Auch hier sollte man genau wissen, was man tut, um kein undefiniertes Verhalten hervorzurufen. Der const_cast ist der einzige C++-Cast, mit dem man das const entfernen kann. Bei einem C-style Cast könntest Du versehentlich ein const entfernen, ohne dass Du das wolltest.

    Wenn einem beim static_cast zuwenig Typprüfung stattfindet und man sich mit Zeigern und polymorphen Klassenhierarchien auseinandersetzt, kann man auch dynamic_cast verwenden. Hier findet ein zusätzlicher Laufzeit-Test statt, der ggf in einem Nullzeiger oder einer Ausnahme enden kann.



  • davie schrieb:

    Das Paradebeispiel ist:

    struct A { };
    struct B : A { };
    struct C : A { };
    struct D : B, C { };
    
    int main () {
      D d;
      A* a1 = reinterpret_cast<A*>(&d); //erlaubt
      A* a2 = (A*)(&d); //ill-formed
    }
    

    Da A,B,C,D in derselben Vererbungshierarchie stecken, ist der C-style cast hier äquivalent zu einem static_cast und nicht einem reinterpret_cast. Und der static_cast würde hier ebendso wenig wegen der Mehrdeutigkeit kompilieren.



  • Super. Danke schön. 👍


Log in to reply