Funktionierende Zuweisung unterschiedlicher Typen - Eine Handleklasse wirft Fragen auf



  • Hallo liebe C++-Community :),

    da ich momentan ein paar Dinge ausprobiere, bin ich nun auch in Kontakt mit Handleklassen gekommen. Beim testen einer Handleklasse, die ich im Großen und Ganzen dem C++-Primer entnommen habe, bin ich über ein mir nicht so recht erklärbares Verhalten gestoßen, das in diesem Buch auch nicht wirklich erklärt wird. Hier erstmal ein (hoffentlich kurz genug gehaltener) kompilierbarer Beispielcode:
    abstractclass.h

    #ifndef MYABSTRACTCLASS_H
    #define MYABSTRACTCLASS_H
    
    #include <cstddef>
    
    class AbstractClass {
    public:
    	virtual AbstractClass* clone() const = 0;
    	virtual void doSomething() const = 0;
    	virtual ~AbstractClass() { }
    };
    
    class AbstractClassHandle {
    public:
    	AbstractClassHandle();
    	AbstractClassHandle(const AbstractClass &abs);
    	AbstractClassHandle(const AbstractClassHandle &orig);
    	~AbstractClassHandle();
    	AbstractClassHandle& operator=(const AbstractClassHandle &rhs);
    	const AbstractClass* operator->() const;
    	const AbstractClass& operator*() const;
    private:
    	AbstractClass *m_pointer;
    	std::size_t *m_use;
    	void decrementUse();
    };
    
    class DerivedClass: public AbstractClass {
    public:
    	virtual DerivedClass* clone() const;
    	virtual void doSomething() const;
    };
    
    #endif // MYABSTRACTCLASS_H
    

    abstractclass.cpp

    #include <iostream>
    #include <stdexcept>
    #include "abstractclass.h"
    
    AbstractClassHandle::AbstractClassHandle(): m_pointer(0), m_use(new std::size_t(1)) { }
    
    AbstractClassHandle::AbstractClassHandle(const AbstractClass &abs): m_pointer(abs.clone()), m_use(new std::size_t(1)) { }
    
    AbstractClassHandle::AbstractClassHandle(const AbstractClassHandle &orig): m_pointer(orig.m_pointer), m_use(orig.m_use)
    {
    	++*m_use;
    }
    
    AbstractClassHandle::~AbstractClassHandle()
    {
    	decrementUse();
    }
    
    AbstractClassHandle& AbstractClassHandle::operator=(const AbstractClassHandle &rhs)
    {
    	++*rhs.m_use;
    	decrementUse();
    	m_pointer = rhs.m_pointer;
    	m_use = rhs.m_use;
    	return *this;
    }
    
    const AbstractClass* AbstractClassHandle::operator->() const
    {
    	if(m_pointer)
    		return m_pointer;
    	throw std::logic_error("unbound handle");
    }
    
    const AbstractClass& AbstractClassHandle::operator*() const
    {
    	if(m_pointer)
    		return *m_pointer;
    	throw std::logic_error("unbound handle");
    }
    
    void AbstractClassHandle::decrementUse()
    {
    	if(--*m_use == 0) {
    		delete m_pointer;
    		delete m_use;
    	}
    }
    
    DerivedClass* DerivedClass::clone() const
    {
    	return new DerivedClass();
    }
    
    void DerivedClass::doSomething() const
    {
    	std::cout << "do something" << std::endl;
    }
    

    main.cpp

    #include "abstractclass.h"
    
    int main()
    {
    	DerivedClass derived;
    	AbstractClassHandle handle;
    	handle = derived; // <-- Wieso klappt die Zuweisung?
    	handle->doSomething();
    
    	return 0;
    }
    

    Wie man am Kommentar in der main.cpp sieht, bereitet mir die Zuweisung des DerivedClass -Objektes an das AbstractClassHandle -Objekt Kopfzerbrechen. Ich habe die Zuweisung bereits mittels des Visual Studio Debuggers Schritt für Schritt versucht nachzuvollziehen. Dabei fiel mir auf, dass zuerst ein temporäres AbstractClassHandle -Objekt mittels des zweiten Konstruktors der AbstractClassHandle -Klasse erzeugt wird und mit diesem dann die Zuweisung durchgeführt wird. Ich verstehe nur nicht so recht wie der Compiler(?) darauf kommt dieses temporäre Objekt für die Zuweisung zu erzeugen. Eigentlich hätte ich damit gerechnet, dass mir eine Fehlermeldung a la:

    Es konnte kein Operator gefunden werden, der einen rechtsseitigen Operanden vom Typ 'DerivedClass' akzeptiert etc.
    

    an den Kopf geworfen wird.

    Ich hoffe das mir jemand dieses Verhalten vielleicht etwas genauer erklären könnte. Und schon mal ein Danke für das Lesen dieses etwas lang geratenen Beitrages 🙂



  • Das Zauberwort heißt Vererbung. Da deine DerivedClass von AbstractClass erbt, ist jedes Objekt der DerivedClass auch ein Objekt vom Typ AbstractClass



  • Die Vererbung ist aber nicht (nur) das interessante 😉
    Mach mal das

    explicit AbstractClassHandle(const AbstractClass &abs);
    

    und wunder dich.



  • Tolpan schrieb:

    Da deine DerivedClass von AbstractClass erbt, ist jedes Objekt der DerivedClass auch ein Objekt vom Typ AbstractClass

    Ja, das ist mir klar, aber das erklärt mir (glaube ich) noch nicht so recht die Zuweisung eines Objektes vom Typ AbstractClass an ein Objekt vom Typ AbstractClassHandle . Schließlich erwartet ja der Zuweisungsoperator von der AbstractClassHandle -Klasse eben auch ein AbstractClassHandle -Objekt. Wieso wird denn dann trotzdem vor der Zuweisung der zweite Konstruktor der AbstractClassHandle -Klasse aufgerufen, um die Zuweisung dennoch durchführen zu können?

    explicit0r schrieb:

    Mach mal das

    explicit AbstractClassHandle(const AbstractClass &abs);
    

    und wunder dich.

    Okay, dadurch bekomme ich nun die erwartete Fehlermeldung:

    error C2679: Binärer Operator '=': Es konnte kein Operator gefunden werden, der einen rechtsseitigen Operanden vom Typ 'DerivedClass' akzeptiert (oder keine geeignete Konvertierung möglich)
    

    Irgendwie verstehe ich jetzt gar nichts mehr 😃



  • explicit besagt, dass eben keine implizite Typkonvertierung stattfindet. Beim Aufruf von

    handle = derived;
    

    wird der Konstruktor von handle aufgerufen, dieser konvertiert derived in das Basisobjekt und übernimmt es. explicit unterbindet nun diese Polymorphie.



  • Mizar schrieb:

    Okay, dadurch bekomme ich nun die erwartete Fehlermeldung:

    error C2679: Binärer Operator '=': Es konnte kein Operator gefunden werden, der einen rechtsseitigen Operanden vom Typ 'DerivedClass' akzeptiert (oder keine geeignete Konvertierung möglich)
    

    Irgendwie verstehe ich jetzt gar nichts mehr 😃

    Was vorher passiert ist:
    Der Compiler sucht einen operator=(const DerivedClass&), wird nicht gefunden.
    Derived ist ein AbstractClass, für den gibt es aber auch keinen operator=. Und jetzt kommt das "explicit", denn der Compiler findet nur den operator=(const AbtractClassHandle&), womit er als nächtes einen Konstruktor für AbstractClassHandle sucht, der mit einem Argument des Typs DerivedClass was anfangen kann - und wird bei AbstractClassHandle(const AbstractClass&) fündig!
    Das explicit im Konstruktor verbietet es nun, implizit diesen Konstruktor aufzurufen. Und schon hast du den Fehler.



  • Gut, das erklärt mir einiges. Vor allem dieser Teil, war mir bis jetzt noch gar nicht bewusst:

    explicit0r schrieb:

    [...]denn der Compiler findet nur den operator=(const AbtractClassHandle&), womit er als nächtes einen Konstruktor für AbstractClassHandle sucht, der mit einem Argument des Typs DerivedClass was anfangen kann - und wird bei AbstractClassHandle(const AbstractClass&) fündig![...]

    Ich dachte bisher, dass der Compiler nur nach einem geeigneten Zuweisungsoperator sucht und wenn er keinen findet sogleich eine Fehlermeldung ausspuckt. Das er jedoch sogar noch weiter nach einem Konstruktor sucht war mir, wie bereits gesagt, noch nicht bekannt. Das erklärt mir nun die Funktionierende Zuweisung vollends. Vielen Dank 🙂


Anmelden zum Antworten