const wegcasten funtioniert nicht



  • Hi, ich möchte in folgender Funktion das const von T* value wegcasten? Blöderweise funktioniert das nicht. Jemand Ideen?

    template <class T>
    void MyClass<T>::AcceptOwner(const T* value) const
    {
    	const Base* obj = dynamic_cast<const Base*>(value);
    	if (obj)
    	{
    		Base* obj2 = const_cast<Base*>(obj);
    		if (obj2)
    		{
    			obj2->ReferenceCounter(_counter);
    		}
    	}
    }
    


  • Arbeite mit mutable um den const_cast wegzukriegen.



  • Ah, super. Genau das habe ich gesucht.



  • Ich bekomme die Funktion allerdings immer noch nicht zum kompilieren. So gehts auch nicht:

    template <class T>
    void MyClass<T>::AcceptOwner(const T* value) const
    {
    	T* obj = const_cast<T*>(value);
    	if (obj)
    	{
    		Base* obj2 = dynamic_cast<Base*>(obj);
    		if (obj2)
    		{
    			obj2->ReferenceCounter(_counter);
    		}
    	}
    }
    


  • const_cast und dynamic_cast sehen schon mal verdächtig aus. Warum nimmt die Funktion nicht einen T* ? Und kannst du den dynamic_cast nicht mit virtuellen Funktionen umgehen?

    Vielleicht grundsätzlich: Was genau willst du erreichen?



  • const wegcasten funktioniert, wenn vorher const hingecastet wurde.

    mach mal ein möglichst kleines aber vollständiges programm mit dem fehler und poste den code. dann können wir das in den compiler stopfen und den fehler wegfummeln. so freistehend fehlt einfach zu viel, es könnte an soo vielen anderen sachen liegen…



  • _counter wird wohl eher das Problem sein, wenn es ein Member ist, ist es in einer const-Funktion const!



  • volkard schrieb:

    const wegcasten funktioniert, wenn vorher const hingecastet wurde.

    Was sowas hier heisst?

    void doShit(SomeClass const * x)
    {
    	SomeClass * y = const_cast<SomeClass*>(x);
    	x->alterState();
    }
    
    void foo()
    {
    	SomeClass obj;
    	doShit(&obj);
    }
    

    Oder wirklich sowas:

    void doShit(SomeClass * x)
    {
    	const SomeClass * cx = const_cast<const SomeClass*>(x);
    
    	SomeClass * px = const_cast<SomeClass*>(cx);
    }
    
    void foo()
    {
    	SomeClass obj;
    	doShit(&obj);
    }
    


  • beides 😉



  • @Skym0sh0: Verboten ist es, wenn das Objekt selbst (nicht Verweise darauf) als konstant deklariert wurde:

    const X x;
    const_cast<X&>(x).modify();
    

    Compiler dürfen z.B. unter Annahme der Konstanz optimieren -> UB



  • Skym0sh0 schrieb:

    volkard schrieb:

    const wegcasten funktioniert, wenn vorher const hingecastet wurde.

    Was sowas hier heisst?

    void doShit(SomeClass const * x)
    {
    	SomeClass * y = const_cast<SomeClass*>(x);
    	x->alterState();
    }
    
    void foo()
    {
    	SomeClass obj;//Zur Laufzeit erstellt im RAM
    	doShit(&obj);
    }
    

    Oder wirklich sowas:

    void doShit(SomeClass * x)
    {
    	const SomeClass * cx = const_cast<const SomeClass*>(x);
    	
    	SomeClass * px = const_cast<SomeClass*>(cx);
    }
    
    void foo()
    {
    	SomeClass obj;////Zur Laufzeit erstellt im RAM
    	doShit(&obj);
    }
    

    genau.

    Nexus schrieb:

    const X x;//Autsch, kann der Compiler ins ROM legen!!!
    const_cast<X&>(x).modify();
    

    Unter ROM kann man da vieles verstehen. Vielleicht bei embedded systems in echte ROM-Bausteine rein. Vielleicht unter Windows ins Code-Segment (was vom Prozessor her als schreibgeschützt markiert wird und bei Speichermangel nicht(!) ausgelagert werden muss, sondern einfach aus dem RAM weggeworfen werden kann; bei Bedarf kann man's aus der exe-Datei ja wieder einlagern). Mir egal. Intel/AMD und gcc/MSVC und c++ einigen sich auf was, was verdammt flotten ausführbaren Code macht, ich muss mich nur an den c++-Standard halten. gcc ist oft so eklig, obwohl er eine bestimmte Optimierung tatsächlich gar nicht macht, zu erkennen, daß er sie als fieser Paragraphenreiter machen dürfte, also daß es UB wäre, und daß er den allerschnellsten erlaubten Code erzeugt, mithin alles wegoptimiert.

    static char c=0;
    for(...){
       if(!++c) cout<<"ich lebe noch"<<endl;//nur alle 256 läufe eine ausgabe
       tuwas(...);
    }
    

    haha, gcc zeigt mir den Effenberg und sagt: Habe Dir nie versprochen, daß nach 127 die -128 kommt, ich optimiere das mal alles fein weg, denn es könnte auf einer hypotetischen Maschine so sein, daß der if-Zweig nie ausgeführt werden würde. Das ist ja allerliebts, daß gcc den schnellsten erlaubten Code erzeugt, echt supi. Aber er soll mir nicht das Wort im Mund herumdrehen. Er tut's aber und ist dabei so gewandt wie wenn Bashar, hustbaer, Arcoth und SeppJ sich zusammentun würden, um mir einen Programmierfehler nachzuweisen.

    Ich musste fast alle meine 80er-Jahre-Tricks fallen lassen, so gemein ist der Compiler zu mir. Naja, aber er zaubert supi schnellen Code.


  • Mod

    es könnte auf einer hypotetischen Maschine so sein, daß der if-Zweig nie ausgeführt werden würde.

    Rein aus interesse: Hast du das mal mit -funsigned-char kompiliert?

    Und das ist leider immer UB, auch wenn char dieselben Werte wie unsigned char annehmen kann. Er gehört laut Standard eben einfach nicht zu den unsigned integer types.



  • Arcoth schrieb:

    Und das ist leider immer UB, auch wenn char dieselben Werte wie unsigned char annehmen kann. Er gehört laut Standard eben einfach nicht zu den unsigned integer types.

    Das ist nicht immer UB. Der Compiler muss vor dem Compile-Vorgang festlegen, ob char sich verhält wie signed char oder unsigned char. Falls er sich für unsigned char entschieden hat, muss das durchgehen.


  • Mod

    faragrasenschreiter schrieb:

    Der Compiler muss vor dem Compile-Vorgang festlegen, ob char sich verhält wie signed char oder unsigned char.

    Der Standard sagt nirgends, dass sich char wie unsigned char verhalten könnte. Er sagt nur, dass char dieselben Werte annehmen könnte. Überläufe sind nach wie vor undefiniert, weil char eben nicht zu der Gruppe von unsigned integers gehört.

    Edit: Es könnte natürlich sein, dass der Standard mit "unsigned integer" einfach einen Typ der nur nicht-negative Werte annehmen kann meint... ja, das klingt sogar sehr plausibel...



  • Ok, Ausgangspunkt ist folgender Programmcode:

    class Base
    {
    public:
    	Base() : _counter(nullptr) {};
    
    	void ReferenceCounter(int* counter) { _counter = counter; };
    
    private:
    	int* _counter;
    };
    
    class Derived : public Base
    {
    public:
    	Derived() {};
    };
    
    template <class T>
    class MyClass
    {
    public:
    	MyClass(T* p);
    
    private:
    	int* _counter;
    };
    
    template <class T>
    MyClass<T>::MyClass(T* value) :
    	_counter(new int(1))
    {
    	Base* obj = dynamic_cast<Base*>(value);
    	if (obj)
    	{
    		obj->ReferenceCounter(_counter);
    	}
    }
    
    int main()
    {
    	MyClass<const Derived> myClass2(new Derived());
    	std::cin.get();
    	return 0;
    }
    

    Problem ist das const in der ersten Zeile der main-Funktion.


  • Mod

    Du kannst auch festlegen, dass das const ignoriert wird:

    template <class T>
    struct MyClass<T const> : MyClass<T>
    {
    	using MyClass<T>::MyClass; // Ohne dieses Feature wird es eher hässlich
    };
    


  • Dein Fehler ist, dass der MyClass -Konstruktor auch Zeiger auf konstante Objekte nimmt, obwohl er Schreibzugriff braucht. Daher entweder std::remove_const oder direkt einen neuen Funktionstemplateparameter einführen.

    Warum dynamic_cast für einen einfachen Upcast, der auch implizit funktioniert? So verlagerst du nur Fehler auf die Laufzeit.

    P.S.: Wenn du einen Smart-Pointer bastelst, kannst du ja mal bei unique_ptr (und evtl. sogar shared_ptr ) schauen, wie das gemacht wurde. Für den produktiven Einsatz würde ich von einem eigenen referenzgezählten Smart-Pointer aber abraten und direkt die beiden verwenden...



  • Richtig, es geht um eine eigene SmartPtr implementierung. Darum kommt auch kein std Kram in Frage. Der dynamic_cast ist evtl. überflüssig, aber es können ja auch Objekte die nicht von Base abgeleitet wurden übergeben werden. Ich wollte deshalb nur sicher gehen. Das ganze habe ich mir von shared_ptr abgeschaut. dort passiert folgendes:

    template<class Y>
        explicit shared_ptr( Y * p ): px( p ), pn( p ) // Y must be complete
        {
            boost::detail::sp_enable_shared_from_this( this, p, p );
        }
    
        template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
        {
            if( weak_this_.expired() )
            {
                weak_this_ = shared_ptr<T>( *ppx, py );
            }
        }
    
    template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe )
    {
        if( pe != 0 )
        {
            pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
        }
    }
    
    template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> * ppx, Y const * py, boost::enable_shared_from_this2< T > const * pe )
    {
        if( pe != 0 )
        {
            pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
        }
    }
    

    Das ganze wollte ich nun auf meine Implementierung übertragen.



  • Enumerator schrieb:

    Der dynamic_cast ist evtl. überflüssig, aber es können ja auch Objekte die nicht von Base abgeleitet wurden übergeben werden. Ich wollte deshalb nur sicher gehen.

    Einfach nichts tun halte ich für eine schlechte Idee. Wenn du den Smart-Pointer auf von Base abgeleitete Klassen auslegst, solltest du eher falsche Benutzung verhindern, z.B. mit static_assert in der Klassendefinition.

    Enumerator schrieb:

    Das ganze habe ich mir von shared_ptr abgeschaut. dort passiert folgendes:

    shared_ptr hat aber einen separaten Reference-Counter, der nicht im Pointee-Objekt eingebaut ist. Daher kann er auch konstante Objekte im Konstruktor annehmen. In deinem Fall solltest du eher das Design überdenken als const leichtsinnig wegzucasten.

    Wenn du wirklich Reference-Counter innerhalb der Pointee-Objekte willst (wodurch die Benutzerfreundlichkeit sinkt), schau dir boost::intrusive_ptr an.


Log in to reply