Forum: PC-Programmierung c++ qt : derived class base virtual Verständnisproblem


von Willy (Gast)


Lesenswert?

error: cannot convert from pointer to base class ‘QGraphicsItem’ to 
pointer to derived class ‘MyBaseClass’ because the base is virtual

Ich hab nen Problem dabei die Fehlermeldung zu verstehen. Was ist damit 
gemeint?


class MyBaseClass : public QObject, virtual public QGraphicsItem {
    Q_OBJECT
[...]
}

class Something :  public MyBaseClass {
    Q_OBJECT
[...]
}


Was ich (glaube) soweit verstanden zu haben:
Ich will hier bei MyBaseClass virtual benutzen, damit ich das 
Diamond-Problem umgehen kann. Die Q_OBJECT Macros brauche ich damit die 
connect/Signal/Slot Kommunikation funktioniert.

Nur woran hängt das jetzt?

von Dirk K. (merciless)


Lesenswert?

Wovon erbt QGraphicsItem denn? Ich habe gerade keine
Qt-Installation hier, um mir die Header anzuschauen.
Die Dokumentation im WWW lässt darauf schließen,
dass QGraphicsItem nicht von QObject erbt. Dann bräuchte
es die virtuelle Vererbung nicht.

merciless

von blablub (Gast)


Lesenswert?

Alternativ kannst Du dir auch einmal QGraphicsObject als Basis angucken, 
das erbt von QObject und QGraphicsItem, dann musst Du dir darüber keine 
Gedanken mehr machen.

http://doc.qt.io/qt-5/qgraphicsobject.html

von Willy (Gast)


Lesenswert?

Danke für Eure Antworten.

Ja, Dirk, da hast Recht, ein QGraphicsItem erbt nicht von QObject. 
Deswegen  ja auch die Mehrfachvererbung (ist das hier der richtige 
Begriff dafür?).
Der Hinweis von Dir, blablup, ist auch gut, daran hatte ich nicht 
gedacht, ich muss mal schauen, ob es noch "Nebenwirkungen" hat, sonst 
ist es vielleicht wirklich die beste Lösung meine Basisklasse als 
QGraphicsObject anzulegen.

Mir gehts aber garnicht so sehr darum, das aktuelle Problem zu lösen, 
das hab ich irgendwie heute Nacht per Zufall doch noch geschafft. Aber 
bitte fragt mich nicht warum es jetzt geht. Ich hab solange die 
Reihenfolge und Keyworte in den beteiligten Klassen verändert bis es 
funktioniert hat. Nach einigem Rumspielen gehe ich davon aus, dass es 
nur daran liegt, dass ich das virtual in der Basisklasse rausgenommen 
habe. Ob ich mir damit an anderer Stelle etwas kaputtgemacht hat, kann 
ich noch nicht absehen.

Ich würde viel lieber verstehen, was das grundlegende Problem ist.
Wann muss ich virtual benutzen, welche Vorteile bringt es, und wie gehe 
ich damit um wenn der Compiler sagt "Nein".

Also zurück zum abstrakten Problem: Was mache ich wenn ich so eine 
Meldung bekomme?

error: cannot convert from pointer to base class ‘QGraphicsItem’ to
pointer to derived class ‘MyBaseClass’ because the base is virtual

Angenommen ich möchte am Ende Objecte A, B und C haben.
Alle sollen Methode x() und y() haben, nur B und C z();
x() ist für alle abgeleiteten Klassen gleich, y() hängt aber von der 
jeweiligen Klasse ab.
Alle brauchen Q_OBJECT Eigenschaften und sollen ebenfalls QGraphicsItems 
sein (diese Anforderung würde wahrscheinlich durch QGraphicsObject 
gelöst, aber ignorieren die Existenz dieser Klasse mal für das 
Gedankenspiel).

Aus meiner Anfängersicht hätte ich jetzt gesagt:

es gibt eine Basisklasse: MyBase, diese erbt von QOBJECT und virtuell 
von QGraphicsItem, x() ist im MyBase definiert, y() wird als virtual 
angegeben.
weiterhin gibt Klassen A,B,C die dann nur noch von MyBase erben müssen.
y() wird dann jeweils in A,B,C implementiert.

Wo liegt mein Verständnisfehler? Warum will der Compiler hier nicht mehr 
casten können?

von BobbyX (Gast)


Lesenswert?

Kaufe dir ein gutes C++ Buch.

von A. H. (ah8)


Lesenswert?

Ich würde sagen, Dein Problem hat nichts mit QT zu tun, sondern mit der 
virtuellen Vererbung. Bei einer normalen (einfachen) Vererbung liegen 
die Attribute der abgeleiteten Klasse im Speicher unmittelbar hinter 
denen der Basisklasse. Die Basisklasse wird – vereinfacht gesagt – 
einfach nach hinten erweitert. Der Anfang des Speicherbereiches ist für 
abgeleitete und Basisklasse gleich, daher kannst Du problemlos zwischen 
beiden hin und her casten. (Du kannst! Ob es sinnvoll ist, ist eine 
andere Frage, die vom konkreten Anwendungsfall abhängt.)

Bei Mehrfachvererbung liegen am Anfang des Speicherbereiches der 
abgeleiteten Klasse die Instanzen mehrerer Basisklassen. Hier sind der 
Anfang des Speicherbereiches von Basisklasse und abgeleiteter Klasse nur 
noch für eine Basisklasse gleich. Da aber der Offset zu den anderen 
Basisklassen dem Compiler bekannt ist, kann er auch hier zwischen den 
Zeigern auf abgeleitete und Basisklasse (egal welcher) hin und her 
rechnen.

Virtuelle Vererbung macht nun überhaupt erst einmal nur bei 
Mehrfachvererbung über mindestens zwei Ebenen hinweg Sinn und nur dann, 
wenn sich mehrere unmittelbare oder mittelbare Basisklassen eine 
gemeinsame Instanz einer mittelbaren Basisklasse teilen sollen.

Beispiel:
1
struct A {};
2
struct B: virtual public A {}
3
struct C: virtual public A {}
4
struct D: public B, public C {};

Hier teilen sich B und C eine gemeinsame Instanz von A. Damit das 
funktioniert enthalten B und C meines Wissens nach lediglich eine 
Referenz auf A, also einen Zeiger, nicht die Attribute von A selbst. 
Wenn Du nur einen Zeiger auf A hast kannst Du natürlich nicht auf die 
Adresse schließen, wo dieser gespeichert ist, folglich auch nicht auf 
die Adressen von B oder C. Das ist schon deshalb nicht möglich, weil es 
potentiell mehrdeutig wäre. Stell Dir noch folgende Klasse vor:
1
struct E: public B, public D {};

In diesem Fall liegen im Speicher (wahrscheinlich) eine Instanz von B 
(unmittelbare Basisklasse von E), dann noch eine Instanz von B 
(mittelbare Basisklasse von E durch D), dann eine Instanz von C 
(mittelbare Basisklasse von E durch D), die Attribute von D, schließlich 
die Attribute von E. Irgendwo davor oder dahinter folgt noch die 
gemeinsame Instanz von A. Auf welches B sollte der Compiler jetzt 
casten, selbst wenn er die Offsets kennt? Und wenn Du auf E casten 
möchtest, ist die konkrete Instanz, die Du vor Dir hast, wirklich eine 
Instanz von E, oder vielleicht eine Instanz von F oder G, die ihrerseits 
von E erben, was die Offsets (möglicherweise) verschieben würde? Das 
alles kannst Du nur aus dem Zeiger auf A nicht herleiten, daher ist ein 
Casten vom Zeiger einer virtuelle Basisklasse auf eine abgeleitete 
Klasse grundsätzlich nicht möglich.

Viel Text, aber ich hoffe das hilft.

: Bearbeitet durch User
von BobbyX (Gast)


Lesenswert?

Viel Text, hilft nicht, besonders einem Anfänger. Auch beinhaltet der 
Text falsche Informationen. ZB:

"Der Anfang des Speicherbereiches ist für
abgeleitete und Basisklasse gleich, daher kannst Du problemlos zwischen
beiden hin und her casten"

Eben nicht "problemlos". In machen Fällen kann man casten, in manchen 
nicht. Man kann beispielsweise nicht eine Instanz der Basisklasse zur 
abgeleiteten Klasse (down)casten.

von A. H. (ah8)


Lesenswert?

BobbyX schrieb:
> Eben nicht "problemlos". In machen Fällen kann man casten, in manchen
> nicht. Man kann beispielsweise nicht eine Instanz der Basisklasse zur
> abgeleiteten Klasse (down)casten.

Es geht hier nicht um das Casten von Instanzen, sondern um das Casten 
von Pointern. Und natürlich sollte ich einen Pointer nur dann (statisch) 
auf den Typ einer abgeleiteten Klasse casten, wenn ich mir absolut 
sicher bin, dass es sich tatsächlich um einen Pointer auf eine Instanz 
dieser Klasse handelt. Die Betonung liegt allerdings au sollte. 
Können tue ich es in jedem Fall.

von Dirk K. (merciless)


Lesenswert?

Willy schrieb:
> Wo liegt mein Verständnisfehler? Warum will der Compiler hier nicht mehr
> casten können?
Ha, das genaue technische Problem kann ich dir
auch nicht erklären, weil ich das immer eine
Ebene höher gelöst habe: Deadly Diamond
vermeiden. Kann dir auch nur den Tipp geben, ein
gutes Buch über OOP mit C++ zu lesen.

Man muss nicht immer vererben, nur weil das
technisch irgendwie möglich ist ;)

merciless

von BobbyX (Gast)


Lesenswert?

A. H. schrieb:
> BobbyX schrieb:
>> Eben nicht "problemlos". In machen Fällen kann man casten, in manchen
>> nicht. Man kann beispielsweise nicht eine Instanz der Basisklasse zur
>> abgeleiteten Klasse (down)casten.
>
> Es geht hier nicht um das Casten von Instanzen, sondern um das Casten
> von Pointern. Und natürlich sollte ich einen Pointer nur dann (statisch)
> auf den Typ einer abgeleiteten Klasse casten, wenn ich mir absolut
> sicher bin, dass es sich tatsächlich um einen Pointer auf eine Instanz
> dieser Klasse handelt. Die Betonung liegt allerdings au sollte.
> Können tue ich es in jedem Fall.

Meine Aussage gilt sowohl für das Casten von Pointern als auch für die 
Typumwandlung von Instanzen.

Bezüglich des Falles, den du beschrieben hast: Nix "sollte", nix 
"könnte".
Um zu überprüfen ob ein Pointer auf eine Instanz eines bestimmten 
(abgeleiteten) Klassentypes zeigt benutzt man einen dynamic_cast. 
static_cast benutzt man hier nur auf systemen ohne RTTI oder wenn man 
besonders auf Performance achten will.

Aber es ging um deine Aussage, dass man "problemlos hin und her casten 
kann". Das ist schlicht und einfach falsch.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.