Ein Basisklassenzeiger kann auf ein abgeleitetes Klassenobjekt verweisen. Warum ist das Gegenteil nicht wahr?

75

Ein Basisklassenzeiger kann auf ein abgeleitetes Klassenobjekt verweisen. Warum ist das Gegenteil ohne Casting nicht wahr? Logischerweise würde eine Basisklasse nicht genügend Informationen über die abgeleitete Klasse haben, aber eine abgeleitete Klasse sollte auch die Informationen über die Basisklasse haben. Mir fehlen hier einige Grundlagen.

Zuzu
quelle

Antworten:

152

Wenn ich Ihnen sage, dass ich einen Hund habe, können Sie davon ausgehen, dass ich ein Haustier habe.

Wenn ich Ihnen sage, dass ich ein Haustier habe, wissen Sie nicht, ob dieses Tier ein Hund ist, es könnte eine Katze oder vielleicht sogar eine Giraffe sein. Ohne zusätzliche Informationen können Sie nicht sicher annehmen, dass ich einen Hund habe.

In ähnlicher Weise ist ein abgeleitetes Objekt ein Basisklassenobjekt (da es eine Unterklasse ist), sodass ein Basisklassenzeiger darauf verweisen kann. Ein Basisklassenobjekt ist jedoch kein abgeleitetes Klassenobjekt, sodass es keinem abgeleiteten Klassenzeiger zugewiesen werden kann.

(Das Knarren, das Sie jetzt hören werden, ist die Analogie Dehnung)

Angenommen, Sie möchten mir jetzt ein Geschenk für mein Haustier kaufen.

Im ersten Szenario weißt du, dass es ein Hund ist, du kannst mir eine Leine kaufen, jeder ist glücklich.

Im zweiten Szenario habe ich dir nicht gesagt, was mein Haustier ist. Wenn du mir also trotzdem ein Geschenk kaufen willst, musst du Informationen wissen, die ich dir nicht gesagt habe (oder nur raten), du kaufst mir eine Leine, wenn es so ist Es stellte sich heraus, dass ich wirklich einen Hund hatte, über den alle glücklich sind.

Wenn ich jedoch tatsächlich eine Katze hatte, wissen wir jetzt, dass Sie eine schlechte Annahme gemacht haben (Besetzung) und eine unglückliche Katze an der Leine haben (Laufzeitfehler).

ClassCastException Cat

jk.
quelle
6
+1: Beste Antwort bisher, IMHO. Eine einfache Analogie, die das Problem intuitiv veranschaulicht.
Oliver Charlesworth
2
"Für jedes Problem gibt es eine Lösung, die einfach, ordentlich und falsch ist." - HL Mencken. Intuitive Antworten fallen häufig in diese Kategorie, insbesondere OO, die in "realen" Begriffen beschrieben werden. Manchmal zeigt ein Basisklassenzeiger auf ein Objekt vom abgeleiteten Klassentyp, und deshalb können Sie (wie die Frage zeigt) umwandeln.
Fred Nurk
1
@Fred: Die Antwort ist nicht falsch; es ist nie an einer Basisklasse zu Punkt gültig Objekt mit einem Zeiger zu abgeleitet. Wie Sie sagen, ist es aber manchmal gilt eine Basis-Klasse zu werfen Zeiger auf einen Zeiger zu abgeleitet.
Oliver Charlesworth
3
Ein Hund ist ein Tier, aber ein Tier ist nicht unbedingt ein Hund, es könnte eine Giraffe sein. Was sagt diese Analogie? Wenn Tier als Basisklasse betrachtet wird und Hund / Giraffe die abgeleiteten Klassen sind. Da Hund / Giraffe tatsächlich Tiere sind, sollte die Kinderklasse in der Lage sein, auf die Basisklasse zu verweisen? Sollte das nicht der Fall sein?
Zuzu
1
Ja, wenn Sie ein Casting durchführen, weisen Sie den Compiler ausdrücklich an, die Klappe zu halten, da Sie zusätzliche Informationen haben, die dies nicht tun. Ich denke, ich kann die Tiermetapher erweitern, um die Zeiger hinzuzufügen und ein bisschen zu werfen - tragen Sie mit mir
jk.
11

Wir haben zwei Objekte.

class A {
   int a;
};

class B : A {
   int b;
};

Ordnen Sie eine Instanz von zu B. Wir können damit entweder als A*oder als B*.

Ordnen Sie eine Instanz von zu A. Wenn wir es in a umwandeln würden B*, sollte dem Mitglied Platz zugewiesen werden b?

Bill Lynch
quelle
10

Äh, weil die Basisklasse keine abgeleitete Klasse ist.

Wenn Sie einen gültigen Zeiger auf einen Typ haben, sagen Sie, dass das Objekt, auf das verwiesen wird, bestimmte Daten an bestimmten Stellen enthält, damit wir sie finden können. Wenn Sie einen Zeiger auf ein abgeleitetes Objekt haben, garantieren Sie, dass das Objekt, auf das verwiesen wird, alle Datenelemente von Derived enthält. Wenn Sie jedoch auf eine Basis verweisen, ist dies tatsächlich nicht der Fall, und Bad Things Happen ™.

Bei Derived werden jedoch garantiert alle Basisdatenelemente an denselben Speicherorten gespeichert. Aus diesem Grund kann ein Zeiger auf Base tatsächlich auf Derived verweisen.

Hündchen
quelle
1
Es wird nicht garantiert, dass alle abgeleiteten Klassen "alle Basisdatenelemente an denselben Speicherorten haben".
Fred Nurk
In Bezug auf den fraglichen Zeiger glaube ich, dass dies der Fall ist.
Welpe
@Bo: Das wird nur Dinge wie Casting und Funktionsaufrufe komplexer machen. Grundsätzlich funktioniert die zeigerbasierte Vererbung nur auf der Grundlage, dass, wenn ich einen Zeiger auf Base habe, die Variablen vtable und member dort existieren, wo sie in einer regulären Instanz von Base existieren würden.
Welpe
5

Weil eine abgeleitete Klasse alles enthält, was in der Basisklasse enthalten ist. Eine Basisklasse enthält jedoch nicht alles, was in der abgeleiteten Klasse enthalten ist.

Es wird nicht empfohlen, eine Basisklasse in eine abgeleitete Klasse umzuwandeln: Was passiert, wenn Sie versuchen, auf Mitglieder zuzugreifen, die nicht Teil der Basisklasse sind?

Jonathan Wood
quelle
Klare Erklärung
Chandra Shekhar
3

Dies gilt, weil ein Tiger ein Tier ist:

    Animal * pAnimal = new Tiger();

Dies ist nicht gültig, da es nicht wahr ist, dass das Objekt ein Pfeilgiftfrosch ist.

    PoisonDartFrog * pPoisonDartFrog = new GenericFrog();
Andy Thomas
quelle
@DeadMG: Du hast mich mitten in der Bearbeitung erwischt. Ich denke, ein Beispiel hilft, das Konzept zu kommunizieren.
Andy Thomas
1
Würden die beiden heutigen Downvoter drei Jahre später gerne kommentieren, was sie in dieser Antwort als fehlerhaft ansehen?
Andy Thomas
... es ist nicht unbedingt wahr, dass das Objekt ein Pfeilgiftfrosch ist ...
Aryaman
@Aryaman - Es ist sicherlich nicht wahr, mit der vernünftigen Annahme, dass niemand GenericFrog als Unterklasse von PoisonDartFrog definiert hat.
Andy Thomas
2
class Base
{
public:
    int a;
}

class Derived : public Base
{
public:
    float b;
}

Base * pBase = new Base();
pBase->a = 7; // setting the value of a in the base

// make a pDerived that points to the SAME DATA as pBase
Derived * pDerived = pBase;
pDerived->a = 5; // this would be okay, base has a public member 'a'
pDerived->b = 0.2f; // error pBase has no data member b and pDerived
                    // points to the SAME DATA as pBase
YoungJohn
quelle
1

Da C ++ eine statisch typisierte Sprache ist und implizite Konvertierungen von Basis zu Ableitung zulässig sind, würde das Typsystem beschädigt. Bjarne Stroustrup wollte keine Laufzeitfehler "Nachricht nicht verstanden".

Fredoverflow
quelle
Zu der Zeit, als Bjarne Stroustrup mit der Arbeit an C mit Klassen begann, war C nicht einmal in der Lage, einen Funktionsaufruf zu überprüfen :) Aber ja, C ist statisch typisiert, wenn auch nicht sehr stark.
Fredoverflow
1
Aus diesem Grund verstehe ich es nicht, die Verhinderung der Zeigerkonvertierung durch statische Typisierung zu rechtfertigen. Es gibt viele Dinge, die zur Laufzeit schief gehen können ("Nachricht nicht verstanden"?), Wie die Dereferenzierung eines Nullzeigers, und Stroustrups Ziele scheinen in der entgegengesetzten Richtung zu liegen, dem Programmierer zur Laufzeit zu vertrauen.
Fred Nurk
@Fred: Vielleicht warst du noch nie einer dynamisch getippten Sprache ausgesetzt, ich weiß es nicht. Wenn C ++ nicht statisch typisiert wäre, könnte man einfach sagen p->any_method_name(42, "hello", test);, und der Compiler würde zur Kompilierungszeit keine Überprüfung durchführen, wenn es eine solche Methode gibt, die drei Parameter akzeptiert, da er nicht wissen würde, dass p auf eine Instanz einer Klasse verweist eines statisch bekannten Typs. Die Überprüfung würde auf die Laufzeit verschoben, die teurer und offensichtlich weniger zuverlässig ist (aber auf der positiven Seite flexibler - Sie benötigen beispielsweise keine Untertypisierung oder Generika).
Fredoverflow
Ich kenne unter anderem Python.
Fred Nurk
1

Die kurze Antwort

class A{
    public: 
        method1();
};

class B: public A{
    public: 
        method2();
};


int main(){

// Case 1
A* ptr_base = new B();
// Here I can call all the methods in A by ptr_base even though it is assigned B ...
// ... because B is derived from A and has all the information about methods of A
// Case 2
B* ptr_derived = new A(); // this will cause error
// Now here ptr_derived is assigned information of A ...
// ... So with this information can I call (*ptr_derived).method2(); ?...
// ... the answer is No because A does not have information of method2() ...;
// ... thus this declaration loses its meaning and hence error.
return 0;
}
Peri Javia
quelle
0

Weil ein Basisklassenzeiger auf eine Instanz der Basisklasse oder einen beliebigen abgeleiteten Typ verweisen kann. Ein abgeleiteter Zeiger kann nur auf diesen abgeleiteten Typ oder eine Unterklasse davon verweisen.

struct Base {};
struct Derived : Base {};
struct Derived2 : Base {};
Base* p = new Derived(); //Fine, Derived inherits from Base
Derived* d = new Base(); //Not fine, Base is not an instance of nor derived from Derived.
Derived* d2 = new Derived2(); // Also not fine, Derived2 derives from Base, but is not related to Derived.

Was das Warum betrifft: Im Allgemeinen ist der Basiszeiger allgemeiner als der abgeleitete Zeiger. Als solches weiß es weniger über den geerbten Typ. Einem abgeleiteten Zeiger kann ohne Umwandlung kein Zeiger auf einen Basistyp zugewiesen werden, nur weil er nicht erkennen kann, ob der Basiszeiger vom abgeleiteten Typ oder einem seiner untergeordneten Zeiger ist.

Washu
quelle
Das Beispiel diente dazu, zu kommunizieren, warum und wenn Sie sich die Mühe gemacht hätten, die Kommentare zu lesen, würden Sie feststellen, dass darin WARUM steht. Nach dem Punkt wurde eine Klarstellung hinzugefügt, um zu helfen.
Washu
2
@ AndyThomas-Cramer: Der Handel mit Upvotes mit einem anderen Benutzer ist gegen den Geist des Systems und ich bin schockiert zu sehen, dass ein 4k-Rep-Benutzer einen solchen Missbrauch so offen befürwortet.
Fred Nurk
@Fred Nurk - Es war nicht meine Absicht, eine Gegenleistung vorzuschlagen. Meine Gegenstimme war bedingungslos.
Andy Thomas
0

Wenn Sie einem abgeleiteten Klassenzeiger eine Adresse von einem Basisklassenzeiger zuweisen, können Sie einem abgeleiteten Klassenzeiger möglicherweise ein Basisklassenobjekt zuweisen. Sie laufen Gefahr, auf abgeleitete Klassenmitglieder zuzugreifen, wenn Sie keine abgeleitete Klasse haben. Während abgeleitete Klassenmethoden für eine Basisklasse funktionieren würden, würden sie dies nur tun, wenn die Methode nicht auf abgeleitete Klassenmitgliedsdaten zugreifen würde.

Das ist ein großes Risiko.

Deshalb zwingen wir Sie zum Casting, damit Sie den Haftungsausschluss anerkennen müssen, der besagt (Sie können einen dummen Fehler machen, bitte seien Sie vorsichtig).

Lee Louviere
quelle
0

Dies liegt daran, dass "der Typ eines Zeigers der Objekttyp ist, auf den der Zeiger zeigt". Damit,

  1. Wenn wir einen Basistypzeiger (* B) haben:

dann erwarten wir ein Basistypobjekt (und möchten auf seine Funktionen zugreifen) an der Adresse, auf die B zeigt, und wenn wir an dieser Adresse ein abgeleitetes Typobjekt erhalten, können wir auch auf die erforderlichen Funktionen zugreifen. Dies liegt daran, dass der abgeleitete Typ ein Basistyp ist.

  1. Wenn wir einen Typzeiger (* D) abgeleitet haben:

dann erwarten wir ein abgeleitetes Typobjekt an der Adresse, auf die D zeigt, und wenn wir dort ein Basistypobjekt erhalten, können wir nicht auf abgeleitete Klasseninformationen vom Basistypobjekt zugreifen, da der Basistyp kein abgeleiteter Typ ist.

Vicky
quelle
0

Taten sagen mehr als Worte. Auch das Kind kann ein Elternklassenobjekt haben. Wenn Sie Zeiger gut verstehen, sind die Einschränkungen für Sie nicht gegeben. Im folgenden Code wurden beide Werte mit einem untergeordneten Klassenzeiger (der ein übergeordnetes Klassenobjekt hatte) gedruckt. Auch bewiesen, dass durch Drucken ihrer Adresse. Anregungen sind willkommen!

#include<iostream>
using namespace std;
class Baap{
    public:
        int a;
        void hoo(){ cout<<"hoo\n"; cout<<a;}
};

class Beta:public Baap{
    public:
        int a;
        int b;
        void hee(){ cout<<"hee\n"; }
};

int main(){
    Baap baap;
    baap.a=1;
    Beta *beta=(Beta*)&baap;
    baap.a=3;
    beta->hee();
    beta->hoo();
    cout<<"\n beta = "<<beta<<"\n&baap = "<<&baap;
    return 0;
}
//output
 hee                                                                                                                           
 hoo                                                                                                                           
 3                                                                                                                             
  beta = 0x7ffd11dd3834                                                                                                        
 &baap = 0x7ffd11dd3834 
Vinod Singh
quelle
0

Im Allgemeinen kann ein Zeiger eines Typs nicht auf ein Objekt eines anderen Typs zeigen. Es gibt jedoch eine wichtige Ausnahme von dieser Regel, die sich nur auf abgeleitete Klassen bezieht.

In dieser Situation kann ein Zeiger vom Typ BASE*auf ein Objekt vom Typ zeigen Derived, dh ein Basisklassenzeiger kann auf ein abgeleitetes Klassenobjekt zeigen, aber umgekehrt ist dies nicht der Fall, da das Basisobjekt nicht das Unterklassenobjekt ist.

Abhishek Raj Permani
quelle