Unterklassenadresse gleich Adresse der virtuellen Basisklasse?


8

Wir alle wissen, dass bei Verwendung einer einfachen Einzelvererbung die Adresse einer abgeleiteten Klasse mit der Adresse der Basisklasse übereinstimmt. Mehrfachvererbung macht das unwahr.

Macht die virtuelle Vererbung das auch falsch? Mit anderen Worten, ist der folgende Code korrekt:

struct A {};

struct B : virtual A
{
    int i;
};

int main()
{
    A* a = new B; // implicit upcast
    B* b = reinterpret_cast<B*>(a); // fishy?
    b->i = 0;

    return 0;
}

1
reinterpret_castmit Klassen ist immer faul (außer von Klasse zu void*und zurück zur gleichen Klasse).
Hyde

3
"Wir alle wissen, dass bei Verwendung einer einfachen Einzelvererbung die Adresse einer abgeleiteten Klasse mit der Adresse der Basisklasse übereinstimmt", ist eine ziemlich starke Aussage. Sind Sie sicher, dass der Standard dies garantiert?
Hyde

7
Es ist eine interessante Eigenart menschlicher Sprachen, dass kein Satz, der mit "wir alle wissen das" beginnt, wahr ist.
Molbdnilo

5
"C ++ wäre eine ziemlich unpraktische Sprache, wenn wir uns nur auf die Garantien des Standards verlassen würden." Ich entwickle C ++ seit Jahrzehnten nicht mehr wie andere, aber ich musste nie gegen den Standard verstoßen oder mich auf nicht spezifiziertes / undefiniertes Verhalten in meinen Anwendungen verlassen.
Timo

2
@ user1610015 Ihr erster Satz ist bei einigen großen Compilern nicht immer richtig und entspricht nicht dem C ++ - Standard. Daher muss es eine andere Spezifikation (über einen bestimmten Compiler oder ABI) geben, die ihn für Ihren speziellen Fall garantiert.
Öö Tiib

Antworten:


5

Wir alle wissen, dass bei Verwendung einer einfachen Einzelvererbung die Adresse einer abgeleiteten Klasse mit der Adresse der Basisklasse übereinstimmt.

Ich denke, die Behauptung ist nicht wahr. Im folgenden Code haben wir eine einfache (nicht virtuelle) einzelne (nicht mehrfache) Vererbung, aber die Adressen sind unterschiedlich.

class A
{
public:
   int getX()
   {
      return 0;
   }
};

class B : public A
{
public:
   virtual int getY()
   {
      return 0;
   }
};

int main()
{
   B b;
   B* pB = &b;

   //A* pA = dynamic_cast<A*>(pB);
   A* pA = static_cast<A*>(pB);

   std::cout << "The address of pA is: " << pA << std::endl;
   std::cout << "The address of pB is: " << pB << std::endl;

   return 0;
}

und die Ausgabe für VS2015 ist:

The address of pA is: 006FF8F0
The address of pB is: 006FF8EC

Macht die virtuelle Vererbung das auch falsch?

Wenn Sie die Vererbung im obigen Code in virtuell ändern, ist das Ergebnis dasselbe. Selbst bei virtueller Vererbung können die Adressen von Basisobjekten und abgeleiteten Objekten unterschiedlich sein.


Tatsächlich bestätigt g ++ Ihren Fall auch mit bitmodifiziertem
Öö Tiib

@ ÖöTiib void main()ist auch in modernen MSVS-Compilern akzeptabel. Übrigens, danke für den Kommentar. Ich habe den Code aktualisiert.
Gupta

1
Nein, void main()ist nicht akzeptabel. Es muss int main()dem Standard entsprechen. Und bitte entfernen Sie das dynamic_castaus dem Code, es wird dort nicht benötigt und es verursacht Verwirrung.
Geza

2

Das Ergebnis reinterpret_cast<B*>(a);wird nur Punkt zu der einschließe garantiert BAufgabe , awenn das aUnterobjekt und das einschließende BObjekt sind zeiger interconvertible , siehe [expr.static.cast] / 3 des C ++ 17 - Standard.

Das abgeleitete Klasse Objekt ist zeiger interconvertible mit dem Objekt Basisklasse nur dann , wenn das abgeleitete Objekt Standard-Layout , hat keine direkten nicht-statische Datenelemente und das Basisklassenobjekt ist seine erste Basisklasse Subobjekt. [Grundverbindung] /4.3

Wenn eine virtualBasisklasse vorhanden ist, wird eine Klasse vom Standardlayout ausgeschlossen . [Klasse] /7.2 .

Da Beine virtuelle Basisklasse und ein nicht statisches Datenelement vorhanden sind, bwird daher nicht auf das umschließende BObjekt verwiesen , sondern bder Zeigerwert bleibt unverändert a.

Der Zugriff auf das iMitglied, als würde es auf das BObjekt zeigen, hat ein undefiniertes Verhalten.

Alle anderen Garantien stammen von Ihrem spezifischen ABI oder einer anderen Spezifikation.


2

Mehrfachvererbung macht das unwahr.

Das ist nicht ganz richtig. Betrachten Sie dieses Beispiel:

struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};

Beim Erstellen einer Instanz D, Bund Csind jeweils mit ihren jeweiligen Instanz instanziiert A. Es wäre jedoch kein Problem, wenn die Instanz von Ddieselbe Adresse wie ihre Instanz Bund ihre jeweilige Instanz von hätte A. Obwohl dies nicht erforderlich ist, geschieht genau dies beim Kompilieren mit clang 11und gcc 10:

D: 0x7fffe08b4758 // address of instance of D
B: 0x7fffe08b4758 and A: 0x7fffe08b4758 // same address for B and A
C: 0x7fffe08b4760 and A: 0x7fffe08b4760 // other address for C and A

Macht die virtuelle Vererbung das auch unwahr?

Betrachten wir eine modifizierte Version des obigen Beispiels:

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};

Die Verwendung des virtualFunktionsbezeichners wird normalerweise verwendet, um mehrdeutige Funktionsaufrufe zu vermeiden. Daher müssen bei Verwendung der virtualVererbung sowohl Bals auch CInstanzen eine gemeinsame AInstanz erstellen . Beim Instanziieren erhalten Dwir folgende Adressen:

D: 0x7ffc164eefd0
B: 0x7ffc164eefd0 and A: 0x7ffc164eefd0 // again, address of A and B = address of D
C: 0x7ffc164eefd8 and A: 0x7ffc164eefd0 // A has the same address as before (common instance)

Ist der folgende Code korrekt?

Es gibt hier keinen Grund zu verwenden reinterpret_cast, noch mehr, es führt zu undefiniertem Verhalten. Verwenden Sie static_caststattdessen:

A* pA = static_cast<A*>(pB);

Beide Darsteller verhalten sich in diesem Beispiel unterschiedlich. Der reinterpret_castwird pBals Zeiger auf neu interpretiert A, aber der Zeiger pAkann auf eine andere Adresse zeigen, wie im obigen Beispiel (C vs A). Der Zeiger wird bei Verwendung korrekt hochgesendet static_cast.


-2

Der Grund aund bdie Unterschiede in Ihrem Fall sind, dass, da Akeine virtuelle Methode vorhanden Aist, a nicht verwaltet wird vtable. Auf der anderen Seite Bwird a beibehalten vtable.

Wenn Sie upcast zu A, ist der Compiler intelligent genug , um das überspringen vtablefür gemeint B. Und damit der Unterschied in den Adressen. Sie sollten nicht reinterpret_castzurückkehren B, es würde nicht funktionieren.

Versuchen Sie, eine virtualMethode hinzuzufügen , z . B. virtual void foo() {}in, um meinen Anspruch zu überprüfen class A. Jetzt Awird auch ein beibehalten vtable. Downcast ( reinterpret_cast) nach B gibt Ihnen also das Original zurück b.


vtables sind hier nicht relevant.
mfnx

Die Frage von @mfnx OP Subclass address equal to virtual base class address? hat alles mit virtueller Vererbung zu tun. Und virtuelle Vererbung hat alles mit vtables zu tun.
theWiseBro

Das Beispiel von @walnut OP führt eine virtuelle Vererbung durch. Der Grund, warum Casting zu falschen Ergebnissen führt, ist das Fehlen einer vtable in Klasse A. Es ist wahr, dass dies nicht getan werden sollte und illegal ist, aber nun, lassen Sie uns praktisch sein.
theWiseBro

@geza Ja, entschuldige meinen dummen Kommentar. Ich bezweifle jedoch, dass das Hinzufügen virtueller Methoden Asicherstellen wird, dass die Adressen übereinstimmen und die Besetzung entweder in der Theorie oder in der Praxis funktioniert. Ich kann meine Downvote nicht ohne eine Post-Bearbeitung entfernen, da sie gesperrt ist.
Walnuss

"Der Grund, warum a und b unterschiedlich sind in ...": Sie können dieselbe Adresse haben. Eine vtable zu haben oder nicht, bedeutet nicht, dass Sie die gleichen Adressen haben, die es nicht sind. Beachten Sie die Unterschiede, die ich mit clang und gcc im Vergleich zu @gupta mit VS2015 hatte.
mfnx
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.