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?
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
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
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?
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
structA{};
2
structB:virtualpublicA{}
3
structC:virtualpublicA{}
4
structD:publicB,publicC{};
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
structE:publicB,publicD{};
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.
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.
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.
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
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.