2016-06-19 10 views
5

Ich habe ein Ergebnis, dass ich nicht von multiple Vererbung erwartet, virtual Methoden und Zeiger auf Basisklassen.Multiple Vererbung, virtuelle Methoden Kollision und Zeiger aus Basisklassen


Mit d.getStr(), wenn d eine derived Instanz ist, die base_2 Version genannt wird, wie ich erwartet hatte.

Mit p->getStr(), wenn p ist ein Zeiger auf eine derived Instanz (oder einen Zeiger auf base_2 auf eine derived Beispiel zeigt), die base_2 Version genannt wird, wie ich erwartet hatte.

Aber mit p->getStr(), wenn p ein Zeiger auf ein base_1 ist auf ein derived Beispiel zeigt, die base_1 Version genannt wird und ich war überzeugt, die base_2 Version genannt würde (dank der using und die Tatsache, dass getStr() sind virtual Methoden) .

Das folgende ist ein einfaches Beispiel:

#include <iostream> 

struct base_1 
{ 
    virtual std::string getStr() const 
    { return "string from base 1"; } 
}; 

struct base_2 
{ 
    virtual std::string getStr() const 
    { return "string from base 2"; } 
}; 

struct derived : public base_1, public base_2 
{ 
    using base_2::getStr; 
}; 


int main() 
{ 
    derived d; 

    derived * dp = &d; 
    base_1 * bp1 = &d; 
    base_2 * bp2 = &d; 

    std::cout << "from derived:   " << d.getStr() << std::endl; 
    std::cout << "from derived pointer: " << dp->getStr() << std::endl; 
    std::cout << "from base_1 pointer: " << bp1->getStr() << std::endl; 
    std::cout << "from base_2 pointer: " << bp2->getStr() << std::endl; 
} 

Die Ausgabe ist die folgende

from derived:   string from base 2 
from derived pointer: string from base 2 
from base_1 pointer: string from base 1 
from base_2 pointer: string from base 2 

Ich weiß, dass der Anruf von base_2 Version aufzuzwingen, die ich in derived die folgende Methode hinzufügen

std::string getStr() const 
{ return base_2::getStr(); } 

aber meine Fragen sind:

1) Warum ignoriert der Zeiger auf (verweist auf eine abgeleitete Instanz) die using-Anweisung und ruft die base_1-Version von getStr() auf?

2) Gibt es eine Möglichkeit, die base_2 Version von getStr(), zu verhängen, wenn derived weise durch einen base_1 Zeiger verwendet wird, ohne getStr() neu zu definieren?

--- EDIT ---

Danke für die Antworten.

Ich verstehe, dass Sie beschreiben, was passiert, aber meine Zweifel ist: beschreibt die Sprache (der Standard) diesen Aspekt? Oder ist es ein undefinierter Teil?

Ich meine: Wenn ich die using Richtlinie entfernen, erhalte ich einen Kompilierungsfehler (error: request for member getStr is ambiguous), von d.getStr() und von dp->getStr(), da der Compiler nicht weiß, welche Version von getStr() zu wählen.

Aber getStr() sind virtual Methoden. Also (ich war davon überzeugt) sollte ein Basiszeiger die abgeleitete Version von ihnen verwenden. Aber wir haben ein paar kollidierende Methoden.

Aus der Sicht der Sprache (Standard) ist eine base_1 (oder) der Zeiger autorisiert (oder verpflichtet), wählen Sie eine der beiden Versionen der kollidierenden Methoden ignorieren die anderen?

Vielleicht irre ich aber scheint mir, dass auf diese Weise die virtual Methoden als nicht virtual Methoden verwaltet werden.

+1

'using' hilft nur im visibity. Es macht die virtuelle Basisfunktion nicht abgeleitet oder sieht so aus. – Arunmu

Antwort

0

1) Scheint so, als würde Ihr Code genau das tun, was er tun soll. Sie zeigen auf base_1, also erhalten Sie Funktionen von base_1 (oder einer seiner Basisklassen). Dass das Objekt aus base_1 und base_2 besteht, ist zu diesem Zeitpunkt unbekannt, weil Sie auf eine Basisklasse und nicht auf eine abgeleitete Klasse zeigen.

2) Nein, das ist einfach unmöglich. Sie müssen in der Tat getStr() in derived überladen.

+0

danke für die Anser aber, über Punkt (1), beschreiben Sie das Verhalten der Basis/abgeleiteten Klassen mit normalen Methoden; 'getStr()' ist 'virtuelle' Methoden – max66

+0

Das ist egal; Tatsache ist, dass virtuelle Funktionen von nebeneinander liegenden Klassen "springen" können, nur vertikal, wenn Sie ein Klassenrelationsdiagramm zeichnen. – JvO

4

Sie erwarten, dass, wenn Sie das using Stichwort in der folgenden Weise unter Verwendung von:

struct derived : public base_1, public base_2 
{ 
    using base_2::getStr; 
}; 

Dass dies das gleiche wie:

struct derived : public base_1, public base_2 
{ 
    void getStr() 
    { 
     base_2::getStr(); 
    } 
}; 

In diesem Fall ist das Verhalten, das Sie erwarten - - Aufruf von p->getStr(), wenn p ein Zeiger auf eine base_1 ist - würde in der Tat am Ende aufrufen base_2::getStr(). derived Überschreibungen base_1 's getStr(), so base_1 Aufruf' s getStr(), durch einen gewöhnlichen Zeiger, Ergebnisse in derived ‚s getStr() aufgerufen zu werden, die base_2getStr() Methode aufruft.

Dies ist jedoch nicht, was passiert. Das Schlüsselwort using ist kein Alias ​​für das Weiterleiten eines Methodenaufrufs auf diese Weise. Das Schlüsselwort using erstellt keine Methode in der Klasse derived 'd, so dass die Klassenvererbung nicht beeinflusst wird und derived_1getStr() in den Unterabschnitten nicht überschrieben wird. Und deshalb wird das Aufrufen von derived_1 's getStr() nicht auf derived_2' s getStr() aufruft.

2

Das passiert, weil die abgeleitete Klasse 2 VTable-Eintrag für getStr() haben muss, eine für jede Basisklasse, so dass es richtig sowohl base_1::getStr() und base_2::getStr() lösen. Die using Direktive erstellt keinen derived::getStr() Vtable-Eintrag oder ersetzt die Basisklasseneinträge, es wählt nur aus, welcher Basisklasseneintrag verwendet wird. Beim Durchlaufen eines Zeigers auf base_1 "sieht" der Compiler nur die Vtable-Einträge für virtuelle Funktionen von derived und base_1, so dass getStr() zu base_1::getStr() aufgelöst wird. Ihre Lösung zum Hinzufügen einer expliziten getStr() in derived ist wahrscheinlich die sauberste, obwohl es ratsam sein könnte, sie virtuell zu machen, um die Basisklassen aus Gründen der Übersichtlichkeit anzupassen.

+0

danke für die Antwort; Mein Zweifel ist: Die 2 VTables in abgeleiteten Klassen sind durch den C++ - Standard auferlegt oder sind ein Implementierungsdetail?Mit anderen Worten: Das passiert, weil es vom Standard auferlegt wird, oder sind andere Compiler berechtigt, ein anderes Verhalten zu erzeugen? – max66

+0

Die Mechaniken sind ein Implementierungsdetail, aber da die Basisklassen selbst abgeleitete Klassen mit virtuellen Methoden sein können, müssen die Endergebnisse unabhängig vom genauen Mechanismus praktisch identisch sein. Die einzige Alternative ist, dass der Compiler den gesamten Code sieht und herausfinden kann, worauf der tatsächliche Typ des Objekts, auf den verwiesen wird, hinweist, was höchst nicht trivial ist. –