ist const_cast böse?



  • oder ist es lieb? 🙂

    findet der überhaupt in der praxis verwendung? wenn ja, macht mal ein kleines beispiel für eine sinnvolle nutzung und nicht so test spielereien.



  • ne is lieb



  • Ich habe es noch nie verwendet. Für gewöhnlich kommt bei mir in solchen Fällen mutable zum einsatz und selbst das ist äußerst selten.



  • dein 1000. beitrag !
    übrigens ich hab ne bessere diät für dich helium, mach trennkost, also erst den salat danach das fleisch und immer so nach einander essen



  • habe es ein oder zwei mal gebraucht, habe es aber dann mit mutable gelöst Improved Console v2.0 Beta



  • ich habs einmal verwendet - sonst nie.

    da ists aber leider nicht anders möglich zu lösen
    [cpp]
    template<typename Type>
    class RingSmartPointer
    {
    private:
    Type* value;
    mutable RingSmartPointer* next;
    mutable RingSmartPointer* prev;

    public:
    RingSmartPointer(Type* value)
    : value(value)
    {
    next=this;
    prev=this;
    }

    RingSmartPointer()
    : value(0), next(0), prev(0)
    {}

    RingSmartPointer(const RingSmartPointer<Type>& other)
    : value(other.value),next(other.next),prev(const_cast<RingSmartPointer<Type>*>(&other))
    {
    prev->next=this;
    next->prev=this;
    }

    ~RingSmartPointer()
    {
    prev->next=next;
    next->prev=prev;
    if(this==next)
    {
    delete value;
    }
    }

    static void Swap(RingSmartPointer<Type>& a, RingSmartPointer<Type>& b)
    {
    std::swap(a.value,b.value);
    std::swap(a.prev,b.prev);
    std::swap(a.next,b.next);
    }

    const RingSmartPointer<Type>& operator=(RingSmartPointer<Type> other)
    {
    Swap(*this,other);
    return *this;
    }

    Type* operator->()
    {
    assert(value);
    return value;
    }

    const Type* const operator->() const
    {
    assert(value);
    return value;
    }

    Type& operator*()
    {
    assert(value);
    return *value;
    }

    const Type& operator*() const
    {
    assert(value);
    return *value;
    }

    template<typename otherType>
    bool compare(const RingSmartPointer<otherType>& other) const
    {
    assert(value);
    return value==other.value;
    }

    };[/cpp]



  • *push*



  • Wozu gräbst du einen vier Jahre alten Thread aus?


  • Mod

    Mich würde ja interessieren, ob Shade das heute anders (insbesondere ohne const_cast) machen würde - das ist schließlich sehr wohl möglich.



  • const_cast wird auch irgendwo in Effectiv C++ dazu benutzt um Elementfunktion mit ihrer Konstanten Version zu implementieren, also diese aufzurufen:



  • Ich hab das auch mal so verwendet. Ne Funktion die die Adresse eines bestimmten Pixels in einer Surface zurückgibt. Wollte das nicht 2x schreiben, also hab ich die eigentliche Rechnung in die const Funktion geschrieben, und die non-const ruft die const auf, und castet das const vom Zeiger weg.
    Allerdings könnte man eine private "char* GetPointerImpl() const" Funktion machen die eben const ist aber einen non-const Zeiger zurückgibt. Die könnte man dann aus der const und der nicht const Funktion gleichermassen aufrufen...



  • Hi,

    warum musste da überhaupt das const weg ? War das der Rückgabewert ?

    Gruß,

    Simon2.



  • hustbaer schrieb:

    Allerdings könnte man eine private "char* GetPointerImpl() const" Funktion machen die eben const ist aber einen non-const Zeiger zurückgibt. Die könnte man dann aus der const und der nicht const Funktion gleichermassen aufrufen...

    Sozusagen const_cast nachgebaut 😃



  • Simon2 schrieb:

    warum musste da überhaupt das const weg ? War das der Rückgabewert ?

    Na stell Dir doch einfach mal ne eigene Vektor-Implementierung vor, die eine Methode 'at' hat (oder halt 'op[]'). Da will man ja nicht den gesamten Code doppelt schreiben, insbesondere, wenn da mehr als nur eine triviale Rückgabe passiert. Und in solch einem Fall ist ein 'const_cast' ganz praktisch.

    Das sähe dann z.B. so aus:

    T& at(size_type index)
    {
        if (not_in_range(index))
            throw out_of_range_exception();
        return m_data[index];
    }
    
    T at(size_type index) const
    {
        return const_cast<vector*>(this)->at(index);
    }
    


  • Konrad Rudolph schrieb:

    T& at(size_type index)
    {
        if (not_in_range(index))
            throw out_of_range_exception();
        return m_data[index];
    }
    
    T at(size_type index) const
    {
        return const_cast<vector*>(this)->at(index);
    }
    

    Wieso rufst du in deiner konstanten Methode, die nicht konstante auf. Was passiert wenn die nicht konstante an den Elementen was ändert.

    Nicht lieber so:

    T& at(size_type index)
    {
        return const_cast<T&>(static_cast<const vector*>(this)->at(index));
    }
    
    const T& at(size_type index) const
    {
        if (not_in_range(index))
            throw out_of_range_exception();
        return m_data[index];
    }
    


  • KasF schrieb:

    Wieso rufst du in deiner konstanten Methode, die nicht konstante auf. Was passiert wenn die nicht konstante an den Elementen was ändert.

    Hmm, wenn ich in dieser Funktion etwas ändern würde, wäre das natürlich schlecht, dann ginge meine Möglichkeit nicht.

    Nicht lieber so:

    T& at(size_type index)
    {
        return const_cast<T&>(static_cast<const vector*>(this)->at(index));
    }
    
    const T& at(size_type index) const
    {
        if (not_in_range(index))
            throw out_of_range_exception();
        return m_data[index];
    }
    

    Das geht nur, wenn der Rückgabewert der const-at-Funktion eine Referenz ist (ist sie bei Dir), wogegen prinzipiell ja auch nichts spricht. Bei mir war sie das in irgendeinem Code mal nicht, daher ging das nicht. Ich muss aber zugeben, dass ich nicht mehr weiß, wieso es dort keine Referenz war. Deine Variante ist besser.


  • Mod

    Ich halte diese Casterei bei const-Überladungen nicht für besonders erhellend. Wenn die Funktionen hinreichend komplex sind, wird man es nicht tun können, und andernfalls nimmt der nötige Kommentar, warum und weshalb hier dieser Weg über const_cast gewählt wurde und wieso das hier legal ist, ohne weiteres mehr Platz weg. Es ist ja nicht so, als könnte man das Ganze (fast) immer so machen. Letzlich immer nur dann, wenn die Konstantheit des Objekts nicht die Konstantheit der Daten bedingt - und dann ist die Verwendung einer privaten Funktion, wie hustbaer vorschlägt, allemal sauberer und weniger kommentarbedürftig. Nur dann, wenn eine solche private Funktion ohne const_cast implementierbar ist, dürfen wir diese Bezugnahme überhaupt in Betracht ziehen.
    Allerdings ist dieser Punkt 3 in Effektiv C++ ohnehin derjenige mit den meisten Fragezeichen. Mit C++0x (rvalue-Referenzen/Move-Konstruktoren) fliegt dann hoffentlich dieser unsägliche Rat hinsichtlich const-Rückgabe raus.

    oder anders:

    T& at(size_type index)
    {
        return const_cast<T&>(static_cast<const vector*>(this)->at(index));
    }
    

    sowas provoziert doch erst mal ein große HUH???



  • Ja da gebe ich dir Recht camper. Als ich den Code getippt und mir angeschaut habe dachte ich auch erstmal 😮

    Was hat das hiermit auf sich, kannst du das vllt bissl erklären:

    camper schrieb:

    Mit C++0x (rvalue-Referenzen/Move-Konstruktoren) fliegt dann hoffentlich dieser unsägliche Rat hinsichtlich const-Rückgabe raus.



  • finix schrieb:

    hustbaer schrieb:

    Allerdings könnte man eine private "char* GetPointerImpl() const" Funktion machen die eben const ist aber einen non-const Zeiger zurückgibt. Die könnte man dann aus der const und der nicht const Funktion gleichermassen aufrufen...

    Sozusagen const_cast nachgebaut 😃

    Nö, so:

    class foo
    {
    public:
        char const* getPtr(size_t x, size_t y) const
        {
            return getPtrImpl(x, y);
        }
    
        char* getPtr(size_t x, size_t y)
        {
            return getPtrImpl(x, y);
        }
    
    private:
        char* getPtrImpl(size_t x, size_t y) const
        {
            if (...)
                throw ...;
    
            return m_image + (x + y * m_pitch);
        }
    
        //...
        size_t m_pitch;
        char* m_image;
    };
    

    Wo ist da ein cast nachgebaut? Hier macht man sich einzig zu nutze dass das worauf m_image zeigt in einem const Objekt nicht mit const wird, sondern nur m_image selbst.


  • Mod

    KasF schrieb:

    Was hat das hiermit auf sich, kannst du das vllt bissl erklären:

    camper schrieb:

    Mit C++0x (rvalue-Referenzen/Move-Konstruktoren) fliegt dann hoffentlich dieser unsägliche Rat hinsichtlich const-Rückgabe raus.

    Im Grunde geht das los mit der Überschrift: "Use const whenever
    possible" - ein etwas vorsichtigere Formulierung wäre hier schon angebracht, denn dieser Grundsatz erlaubt ja kein Abwägen. Dabei ist es doch aber gerade bei so generellen Grundsätzen wichtig, die Gründe zu kennen. Es mag sein, dass diese in 99% zur richtigen Entscheidung führen, aber in einem hinreichend großen Programm wird man immer eine Stelle haben, an der der allgemeine Grundsatz gerade nicht gilt - diesen Punkt zu erkennen ist nicht weniger wichtig als die Kenntnis der Grundsätze. Aber ich schweife ab.

    Meyers empfiehlt als Rückgabetyp von Funktionen const T um die Fehlerhäufigkeit beim Nutzer ohne Einbuße für Sicherheit und Effizienz zu reduzieren. Also

    class Rational { ... };
    const Rational operator*(const Rational& lhs, const Rational& rhs);
    

    Begründet wird dies mit dem Konstrukt

    Rational a, b, c;
    ...
    a * b = c;
    

    Zwar ist nicht zu erwarten, dass ein einigermaßen seiner Geisteskräfte gewärtiger Programmierer so etwas bewußt schreibt (und wenn es bewusst passiert, muss es ja nicht unbedingt ein Problem sein), allerdings kann so etwas relativ leicht passieren, wenn in einer Bedingung eigentlich

    a * b == c
    

    stehen sollte. Es geht also im Grunde um das alte Problem von Tippfehlern bei == und =. Leider thematisiert Meyers hier nicht, wie relevant diese Möglichkeit in der Praxis wirklich ist, sondern verweist nur darauf, dass so etwas bei built-ins nicht passieren kann.
    Wären a,b,c hier built-ins, würde unser Compiler dieses Konstrukt ablehen - aber NICHT, weil a * b konstant wäre, sondern weil es sich um ein Rvalue handelt. Das ist - jedenfalls in C und C++ - immer noch ein Unterschied.
    Die Problematik hinsichtlich == und = ist nicht neu und beschränkt sich nicht auf eigene Klassen: häufig wird vorgeschlagen, einfach Variable und zu vergleichenden Wert zu vertauschen (das scheint hier auch geschehen zu sein - andererseits ist Meyers jedenfalls in Effective C++ kein Advokat dieser Lösung). Bereits das kann man mit guten Argumenten hinterfragen (schließlich ist 5*5==c ? denklogisch etwas völlig anderes als c==5*5 - ungefähr so, als wenn man Subjekt und Objekt vertauscht). Unser Problem a*b=c wird also schon gar nicht relevant, wenn wir nicht gleichzeitig auch die natürliche Ordnung unseres Vergleichs ändern. Wenn wir versehentlich c=a*b statt c==a*b schreiben, hilft uns das const Rational überhaupt nicht. Zudem liegt das Problem ja eigentlich beim operator= - dieser muss stets als nicht-statische Memberfunktion deklariert werden (was daran liegen dürfte, dass der Compiler früh wissen muss, ob er ihn selbst deklarieren soll - zudem ist die Verdeckung des ererbten Operators wichtig). Nun darf der Objektparameter einer Klassenfunktion aber auch ein rvalue sein - und hierin liegt das Problem. Könnten wir Operator= als freie Funktion implementieren, würden wir für den linken Parameter eine non-const Referenz verwenden. Das Problem ist also unser Operator=, geheilt werden soll das Problem aber durch Änderung des Typs (mehr oder weniger) aller anderen Funktionen. Das sieht also mehr nach der Bekämpfung von Symptomen aus.
    Gibt es andere Möglichkeiten?
    Beim Verhältnis von == zu = fällt auf, dass == das Operationsergebnis im Vordergrund steht während bei der Zuweisung der bewirkte Seiteneffekt motivationsbestimmend ist. Damit bietet sich eine einfache Lösung an:
    wir machen den Rückgabetyp von op= einfach void. In diesem Falle würde c=a*b, wenn es versehentlich statt c==a*b auftaucht, vom Compiler abgelehnt werden, weil es nicht implizit in bool konvertiert werden kann. Wir lösen also das gleiche Problem und sind zudem nicht auf die Umordnung der Operanden angewiesen. Natürlich kostet das Ganze auch etwas: Namentlich Mehrfachzuweisungen werden unmöglich - die sind aber eine vergleichsweise unwesentliche Syntaxerleichterung. Meyers schlägt allerdings in Nummer 11 vor, operator= solle das aufgerufene Objekt per Referenz zurückgeben - unter Hinweis auf die Konvention und das Verhältnis zu eingebauten Typen. Dagegen ist im Grunde nichts einzuwenden, aber im Verhältnis zu der obigen Problematik erscheint die Argumentation ein wenig widersprüchlich. Welches Konstrukt wird denn dadurch einfacher und sicherer, dass wir den Rückgabewert der Zuweisung weiterverwenden? Nach Meyers' Verständnis soll auf der linken Seite der Zuweisung ein benanntes Objekt stehen. Dann ist aber die Verwendung der Mehrfachzuweisung u.ä. unnötig und eine Weiterverwendung des Ergebnisses zumindest bei eingebauten Typen sehr leicht undefiniertes Verhalten. Brauche ich ausnahmsweise doch einmal mehrere Zuweisungen in einem Ausdruck, kommt immer noch der Kommaoperator in Frage.

    Zusammenfassend behaupte ich, dass der Ansatz per const T das Problem von Zuweisung statt Vergleich an der falschen Stelle und zudem unzureichend löst (es gehört ohnehin zu den Problemen, für die man als C++-Programmierer ein Auge entwickeln muss - daher glaube ich nicht, dass es einer Kompensation durch Designänderung überhaupt bedarf). Im Übrigen hat man für diese Dinge Compilerwarnungen erfunden.

    Meyers behauptet zudem, die Verwendung von const T wirke sich nicht negativ auf die Performance aus. In wieweit das bei heutigen Compilern tatsächlich so ist, kann ich nicht einschätzen - es könnte stimmen oder auch nicht mit Blick auf das Problem Argumentübergabe per Referenz-auf-const oder per Value. Mit C++0x wird es allerdings Rvalue-Referenzen geben und dann werden Movekonstruktoren zunehmend eine Rolle spielen (man kann soetwas bereits jetzt bauen, allerdings ist etwas umständlich und nicht so ganz portabel). Die Idee beim Move-Konstruktor ist, dort, wo eine Kopie eines temporären Objektes gemacht werden muss, an Stelle einer (tiefen) Kopie der Daten, diese einfach in das neue Objekt zu verschieben, das alte Objekt wird ja ohnehin danach nur noch zerstört werden. Wir kennen das von auto_ptr, wenn eine Funktion einen auto_ptr zurückgibt. Allerdings verschiebt auto_ptr den Inhalt immer und nicht nur, wenn temporäre Objekte betroffen sind. Für diese Unterscheidung müssen wir beim Funktionsaufruf zwischen konstanten Lvalues und nicht-konstanten Rvalues unterscheiden können, und das ist mit der gegenwärtigen Sprache kaum möglich. In C++0x sieht das dann ungefähr so aus:

    template<typename T> class Vektor
    {
    public:
        Vektor(const Vektor); // konsumiert Lvalues und konstante Rvalues, deep-copy
        Vektor(Vektor&& other) // konsumiert nicht-konstante Rvalues, verschiebt inhalt
        p_( other.p_ ), size_( other.size_ )
        {
            other.p_ = 0; // Klasseninvarianzen dürfen verletzt werden, other muss
                          // nur zerstörbar sein
        }
        Vektor& operator=(const Vektor&);
        Vektor& operator=(Vektor&&);
    private:
        T* p_;
        size_t size_;
    };
    
    Vector<int> f();
    void g()
    {
        Vektor<int> a;
        Vector<int> b=a; // Deep Copy
        b=f(); // Verschiebt den Inhalt des zurückgegebenen Objekts ohne noch einmal zu kopieren
    

    Aber so ein Movekonstruktor (oder Move-Zuweisung) ist natürlich nutztlos, wenn das Argument zwar ein Rvalue aber zusätzlich konstant ist, wie Meyers vorschlägt.


Anmelden zum Antworten