2015-03-15 1 views
15

Zuerst bin ich Java-Programmierer und möchte Polymorphismus in C++ verstehen. Ich schrieb das Beispiel zu Lernzwecken:Polymorphismus in C++: Aufrufen einer überschriebenen Methode

#include<iostream> 

using namespace std; 

class A 
{ 
public: 
    virtual void foo(){ std::cout << "foo" << std::endl; } 
}; 

class B : public A 
{ 
public: 
    void foo(){ std::cout << "overriden foo" << std::endl; } 
}; 

A c = B(); 

int main(){ c.foo(); } //prints foo, not overriden foo 

ich erwartet, dass overriden foo würde gedruckt, aber es war nicht. Warum? Wir übergingen die Methode foo in der class B und ich dachte, dass die Entscheidung, welche Methode aufgerufen werden sollte, aus dem Laufzeittyp des Objekts ist, das in meinem Fall B ist, aber kein statischer Typ (A in meinem Fall).

Live-Beispiel ist there

+0

[. Bitte verwenden Sie nicht 'using namespace std;'] (http : //stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice) – orlp

+0

Sie müssen auf 'foo()' durch einen 'A *' Zeiger oder 'A &' Bezug zurückgreifen. –

+1

@ πάνταῥεῖ Könntest du dir nicht erklären, was du sagst? Ok, es funktioniert, aber warum gibt es so einen Unterschied? Ich kann das nicht für mich auf eine logische Art erklären ... – stella

Antwort

23

Wenn Sie dies tun:

A c = B(); 

Sie sind der B Wert in A umwandelt. Das willst du nicht.

Sie sollen ein B Objekt machen und darauf zugreifen über ein AZeiger oder Referenz polymorphes Verhalten zu erhalten:

B b; 
A& c = b; 
+0

Meinst du, dass ich gerade die Kopie angerufen habe? Konstruktor, der das Objekt der Runti erzeugt ich schreibe 'A'? – stella

+1

@stella Sie haben eine implizite Umwandlung von B -> A verursacht, wenn Sie 'A c = B();'. – orlp

+0

Ehrlich, ich grabe in C++ 11 Standard Kapitel 4. Ich würde sagen, dass es in der Implementierung des Kopierkonstruktors ist, der einige Sachen macht und das Objekt des Laufzeittyps A erzeugt. Ich bin mir nicht sicher , aber warum nicht? – stella

1

genau das, was die orlp. Sie sollten auch Zeiger lernen zu verwenden (sie sind Spaß)

A* c = new B(); 
c->foo(); // overriden foo 
delete c; 
14

in Java, Sie haben Wert Semantik mit Typen wie int und float, und Sie haben Referenzsemantik mit allem anderen.

Das ist nicht der Fall in C++ ist: das Typsystem ist einheitlich, und Sie erhalten je nachdem, was von Wert oder Referenzsemantik, die Sie für fragen .

Mit dem Code, den Sie geschrieben haben

A c = B() 

Sie haben den Compiler gesagt einen neuen Wert vom Typ B, zu erstellen und dann konvertieren es auf einen Wert von Typ A, Speichern des Wert in c. Umwandlung bedeutet in diesem Fall, dass Sie A Daten aus der neuen B Instanz, die Sie erstellt haben, und Kopieren in die neue A Instanz in c gespeichert haben.

Sie können dies stattdessen tun:

B b; 
A &c = b; 

Dieses immer noch die eine Referenz-A, was bedeutet, c ist Wertb, aber jetzt schafft c jetzt beziehen Sie auf die B Instanz erstellt, anstatt eine Kopie seines A Teils zu sein.

Nun wird b als lokale Variable erstellt, und das in b gespeicherte Objekt wird zerstört, sobald b den Gültigkeitsbereich verlässt. Wenn Sie etwas hartnäckigeres wollten, müssten Sie Zeiger verwenden; z.B. so etwas wie

shared_ptr<A> c = make_shared<B>(); 
c->foo(); 

Sie etwas mehr 'raw' wie

A *b = new B(); 

aber dies ist ein 'dumm' Zeiger tun könnte; shared_ptr ist schlauer und Ihr Objekt wird zerstört, wenn nichts anderes darauf verweist. Wenn Sie das letztere tun, würden Sie die Zerstörung selbst tun müssen, wenn angemessen (und dies vermasselt ist eine häufige Fehlerquelle)

+0

C++ - Typ-System ist definitiv nicht vereinheitlicht und Wert/Referenz-Semantik sind orthogonal zu dem Problem. – Voo

+1

@Voo: True, aber es ist mehr als Java. Ich habe das OP als verwirrt gelesen und erwartet, dass 'A c' eine Variable mit 'Bezug auf' A' Semantik deklariert, wie es in Java wäre; Ich nehme an, es könnte es anders lesen, obwohl ich es nicht sehe. – Hurkyl

+0

In Java hat alles außer Primitiven einen Eltern-Typ 'Object', sicher nicht vollständig, aber C++ hat überhaupt nichts dergleichen. Ich glaube, Sie missverstehen, was ein "einheitliches Typsystem" ist (z. B. Scala hat ein echtes einheitliches System mit "Any"). C++ hat keinen und will keinen und es gibt legitime Gründe dafür. – Voo

4

Die Linie von Interesse ist dies (unter Anwendung einheitlicher Initialisierung Syntax statt):

A c = B{}; 

Es ist wichtig zu beachten, dass c, wenn auf diese Weise deklariert, sich wie ein Wert verhält.

Ihr Code erstellt einen lokalen A mit dem Namen c aus einer Instanz von B. Dies wird Slicing genannt: Jeder Teil von diesem B, der nicht Teil einer A ist, wurde weggeschnitten, so dass nur noch ein A übrig bleibt.

Die Angabe einer Symbolverweis-Semantik in C++ (als Indirection bezeichnet) erfordert eine andere Schreibweise.

Zum Beispiel:

A &c = B{}; 
A d = B{}; // Casts the intermediate B instance to const A &, 
      // then copy-constructs an A 

c.foo(); // calls B::foo because c points to a B through an A interface 

d.foo(); // calls A::foo because d is only an instance of an A 

Hinweis, daß die Lebensdauer des Zwischen B zu dem c Punkte automatisch auf den Umfang der c ausgefahren ist. Auf der anderen Seite wird das zweite Zwischenprodukt B zerstört, nachdem der Bau von d abgeschlossen ist.

In C++ sind Verweise unveränderlich (sie können nicht nach der Initialisierung geändert werden). Wenn in einem Ausdruck verwendet, es ist, als ob das Objekt (Wert), an die sie wurden stattdessen weisen verwendet:

A &c = B{}; 

c = A{}; // Calls the compiler-provided A::operator = (const A &) 
     // (a virtual assignment operator with this signature 
     // was not defined). 
     // This DOES NOT change where c points. 

Pointers, auf der anderen Seite, kann geändert werden:

A a{}; 
B b{}; 

A *cptr = &b; 

cptr->foo(); // calls B::foo 

cptr = &a; 

cptr->foo(); // calls A::foo 
7

Ihr Verwirrung entsteht durch einen entscheidenden Unterschied zwischen Java und C++.

In Java, wenn Sie schreiben

MyClass var = whatever; 

Ihre Variable var eine Referenz auf das von whatever zurückgegebene Objekt. In C++ bedeutet diese Syntax jedoch "Erstellen Sie ein neues Objekt vom Typ MyClass, indem Sie das Ergebnis des Ausdrucks whatever an einen geeigneten Konstruktor übergeben und das resultierende Objekt in die Variable var kopieren.

Insbesondere schafft Ihr Code ein neues Objekt vom Typ A, genannt c und übergibt ein temporären Standard konstruierten Objekte vom Typ B seines Copykonstruktor (weil das der einzige Konstruktor ist, das paßt). Da das neu angelegte Objekt vom Typ A ist, nicht vom Typ B, wird natürlich A 's Methode foo aufgerufen. Wenn Sie eine Referenz auf ein Objekt haben möchten, müssen Sie dies explizit in C++ anfordern, indem Sie & zum Typ hinzufügen. Ein Verweis auf nicht konstante Objekte kann jedoch nicht an Provisorien gebunden werden. Daher müssen Sie auch explizit das Objekt deklarieren, an das Sie binden (oder alternativ einen Verweis auf ein const-Objekt verwenden und Ihre foo-Elementfunktionen auf const fixieren, da sie das Objekt ohnehin nicht ändern). So ist die einfachste Version des Codes zu tun, was würden Sie lesen möchten:

// your original definitions of A and B assumed here 

B b; // The object of type B 
A& c = b; // c is now a *reference* to b 
int main() { c.foo(); } // calls B::foo() thanks to polymorphism 

jedoch die bessere Version const zu korrigieren, wäre und könnte dann Ihre ursprünglichen Konstruktion verwenden:

#include <iostream> 

class A 
{ 
public: 
    virtual void foo() const // note the additional const here! 
    { std::cout << "foo" << std::endl; } 
}; 

class B : public A 
{ 
public: 
    void foo() const // and also const here 
    { std::cout << "overridden foo" << std::endl; } 
}; 

A const& c = B(); // Since we bind to a const reference, 
        // the lifetime of the temporary is extended to the 
        // lifetime of the reference 

int main() { c.foo(); } //prints overridden foo 

(beachten Sie, dass ich jedoch entfernt using namespace std; weil es eine schlechte Sache zu tun (und Verwendung im Code explizit std:: sowieso, es ist so einfach redundant) ist.

Beachten sie, dass C++ Referenzen aus Java Referenzen, dass nach wie vor unterschiedlich sind sie nicht neu zugewiesen werden können ed; Jede Zuweisung wird stattdessen an das zugrunde liegende Objekt übergeben. Zum Beispiel:

#include <iostream> 

class A { public: virtual void foo() const { std::cout << "I'm an A\n"; } }; 
class B: public A { public: void foo() const { std::cout << "I'm a B\n"; } }; 
class C: public A { public: void foo() const { std::cout << "I'm a C\n"; } }; 

B b; 
C c; 

int main() 
{ 
    A& ref = b; // bind reference ref to object b 
    ref.foo(); // outputs "I'm a B" 
    ref = c; // does *not* re-bind the reference to c, but calls A::operator= (which in this case is a no-op) 
    ref.foo(); // again outputs "I'm a B" 
} 

Wenn Sie das Objekt, das Sie beziehen sich ändern wollen, müssen Sie Zeiger verwenden:

// definitions of A, B and C as above 
int main() 
{ 
    A* prt = &b; // pointer ptr points to b 
    prt->foo(); // outputs "I'm a B" 
    prt = &c; // reassign ptr to point to c 
    prt->foo(); // outputs "I'm a C" 
}