2011-01-02 6 views
5

Ich habe mehrere Vererbung in C++ für eine ziemlich lange Zeit verwendet, aber nur heute realisiert, dass dies bedeuten könnte, dass die Zeiger-Adressen abweichen könnten, wenn sie als eine der Unterklassen verweisen.Unterklasse Casting und Zeigeradresse Änderungen

Zum Beispiel, wenn ich habe:

class ClassA{ 
    public: 
     int x; 
     int y; 
     ClassA(){ 
      cout << "ClassA : " << (unsigned int)this << endl; 
     } 
    }; 

    class ClassC{ 
    public: 
     int cc; 
     int xx; 
     ClassC(){ 
      cout << "ClassC : " << (unsigned int)this << endl; 
     } 
    }; 

    class ClassB : public ClassC, public ClassA{ 
    public: 
     int z; 
     int v; 
     ClassB(){ 
      cout << "ClassB : " << (unsigned int)this << endl; 
     } 
    }; 


    int main(){ 

    ClassB * b = new ClassB(); 

    } 

Klasse A und Klasse C haben unterschiedliche Adressen, wenn auf dem Konstruktor gedruckt.

Doch wenn ich versuche, sie zu ihnen zurück zu werfen, es funktioniert einfach automagically:

ClassA * the_a = (ClassA*)b; 
cout << "The A, casted : " << (unsigned int)the_a << endl; 

ClassB * the_b = (ClassB*)the_a; 
cout << "The B, casted back : " << (unsigned int)the_b << endl; 

ich diese Art von Informationen annehmen kann vom Compiler aus dem Code abgeleitet werden, jedoch ist es sicher zu angenommen, dass dies auf allen Compilern funktioniert?

Zusätzliche Frage: Ist es möglich, die Reihenfolge der Unterklassen zu erzwingen? Wenn ich zum Beispiel brauche, dass classA zuerst lokalisiert wird (im Wesentlichen die gleiche Zeigerstelle) wie ClassC, die es abstuft, muss ich es nur in die Deklaration von Unterklassen einfügen? Update Okay, sieht so aus, als ob es nicht möglich ist, die Bestellung zu erzwingen. Ist es noch möglich, die "Wurzel" -Adresse der Struktur, den Anfang der Adresse, die der Unterklasse zugewiesen ist, auf der Oberklassenebene herauszufinden? B. die Adresse von classB von ClassA erhalten.

+0

Ich glaube nicht, dass Sie die _ "root" _ Adresse erhalten können. Ich würde jedoch eine separate Frage dazu stellen. AFAICT aus dem Standard, ist es nicht einmal garantiert, dass Basisklasse Mitglieder oder Unterobjekte in niedrigeren Speicheradressen platziert werden. Mit anderen Worten, es gibt keine Garantie dafür, dass "the_a" eine niedrigere Adresse hat als "the_b" oder irgendetwas von der Art. –

Antwort

6

Das ist eine perfekte Standardverwendung von Mehrfachvererbung, und es wird mit jedem kompatiblen Compiler funktionieren. Sie sollten jedoch beachten, dass im ersten Fall eine explizite Umwandlung überflüssig ist und dass die C-Stil-Umwandlung in der zweiten durch eine static_cast ersetzt werden kann.

Ist es möglich, die Reihenfolge der Unterklassen zu erzwingen?

Nein: Das Layout ist implementiert und Ihr Code sollte nicht von dieser Reihenfolge abhängen.

+0

Ich sehe. Gibt es dann einen möglichen Weg für die Oberklasse (sagen wir Klasse A), die Wurzeladresse der gesamten Struktur zu erhalten (die Adresse der Klasse A, wenn sie selbst ist, oder die Adresse der Klasse B, wenn sie Teil der Klasse B ist)? Ich erstellte eine Speicherpoolimplementierung, die von der Stammadresse abhing. – kamziro

+0

@kamziro: eigentlich glaube ich nicht, dass es ist, aber ich muss sagen, ich bin überrascht, dass Sie sogar brauchen, dass – icecrime

+0

ich sehe. Meine Speicherpoolimplementierung durch Unterklassen ist erforderlich, um die freie Adresse an den Pool zurückzugeben und die gespeicherte Basisadresse nicht um 4 Byte zu speichern. Vier mickrige Bytes .. Ich denke, wenn es keine Wahl gibt, dann schade :) – kamziro

3

Ja, Sie können davon ausgehen, dass dies sicher ist. Ein Zeiger Typcast in C++ wird garantiert, um den Zeiger korrekt anzupassen, um die Konvertierung von Base zu abgeleitetem oder umgekehrt zu berücksichtigen.

Das heißt, Sie müssen aufpassen, das System nicht zu weit zu schieben. Zum Beispiel wird der Compiler nicht dieses Wandlungsrecht erhalten:

ClassB* b = new ClassB; 
ClassC* c = (ClassC*)(void*)b; 

Dies bricht, weil die Besetzung von C nach A durch ein void * geschleust werden, und so die Informationen darüber, wo sich der Zeiger innerhalb des B-Objekt ist, hat verloren.

Ein weiterer Fall, in dem ein Straight Cast nicht funktioniert, ist die virtuelle Vererbung. Wenn Sie von einer abgeleiteten Klasse in eine virtuelle Basis oder umgekehrt umwandeln möchten, müssen Sie den Operator dynamic_cast verwenden, um sicherzustellen, dass die Umwandlung erfolgreich ist.

+0

Danke für die Korrektur! Das kommt so selten vor, dass ich nicht viel Gelegenheit hatte, damit zu experimentieren. – templatetypedef

+0

Guter Aufruf über virtuelle Vererbung, die ein heikles Thema ist. Ich wusste nicht, dass du 'dynamic_cast' verwenden musst, aber du tust es. Siehe C++ 03 §5.2.9 Absätze 5 und 8, in denen ausdrücklich gefordert wird, dass Vererbung nicht virtuell ist, wenn von einer Basis in eine abgeleitete Klasse umgewandelt wird. –

1

Ja, eine naive Implementierung für virtuelle Basen besteht darin, an einem bekannten Ort im abgeleiteten Objekt einen Zeiger auf das Unterobjekt der virtuellen Basis zu platzieren.

Wenn Sie mehrere virtuelle Basen haben, ist das ein bisschen teuer. Eine bessere Darstellung (von Microsoft patentiert, denke ich) verwendet selbst-relative Offsets.Da diese für jedes Unterobjekt invariant sind (dh sie hängen nicht von der Objektadresse ab), können sie einmal im statischen Speicher gespeichert werden, und Sie benötigen nur einen einzigen Zeiger auf sie in jedem Unterobjekt.

Ohne eine solche Datenstruktur würden Crosscasts nicht funktionieren. Sie können die Umwandlung von einer virtuellen Basis A zu einer virtuellen Basis B innerhalb der virtuellen Basis A kreuzen, obwohl das B-Subobjekt nicht sichtbar ist (vorausgesetzt, nur die beiden Basen haben eine gemeinsame virtuelle Basis X, wenn ich mich richtig erinnere). Das ist ziemlich haarige Navigation, wenn man darüber nachdenkt: Die Navigation erfolgt über den Formdeskriptor der am weitesten abgeleiteten Klasse (die sowohl A als auch B sehen kann).

Was noch haariger ist, ist, dass die so dargestellte Struktur "nach dem Gesetz des Standards" erforderlich ist, um sich während der Konstruktion und Zerstörung dynamisch zu verändern, weshalb die Konstruktion und Zerstörung komplexer Objekte langsam ist. Wenn sie einmal gemacht sind, sind Methodenaufrufe, sogar Queranrufe, ziemlich schnell.