2010-12-16 5 views
1

ich folgende Klassen zur Verfügung zu stellen:Wie mehr als eine Überschreibung für eine virtuelle Funktion

class A { 
}; 

class B : public A { 
}; 

class P  { 
private: 
std::list<A*> l 
protected: 
virtual void DoIt(A* a) = 0; 
public: 
void WorkerThread() { for (it=l.begin(); it!=l.end(); it++) DoIt(*it); } 
}; 

class Q : public P 
{ 
protected: 
void DoIt(A* a) { print("false"); } 
void DoIt(B* b) { print("true"); } 
}; 

Leider wird DoIt (B * b) nie aufgerufen. DoIt (A * a) wird immer aufgerufen, auch wenn ich B-Objekte zur Liste hinzufüge.

Was kann ich tun, damit DoIt (B * b) aufgerufen wird? Kann dies erreicht werden, wenn B Q nicht kennt? Ist dies ohne Dynamik möglich?

Danke

+0

Wie können beide DoIt-Methoden in Q existieren, ohne als mehrdeutig zurückgewiesen zu werden? –

+0

@David - weil sie nicht sind. –

+2

Auch sind die STL-Container nicht wirklich gebaut, um geerbt zu werden (wie P, oben). Verwenden Sie stattdessen die Komposition. – Joe

Antwort

4

Nun, niemand ist wirklich direkt beantwortet Ihre Frage (na ja, Heavyd versucht), also werde ich. Einige andere "Antworten" sind hier hilfreicher, um Ihr Problem zu beheben.

Das Problem ist, dass void DoIt (B *) ist keine Überschreibung der virtuellen Funktion DoIt (A *). Es ist eine Überladung. Es ist ein großer Unterschied.

Wenn Sie sagen, dass DoIt (B *) nicht aufgerufen wird, wenn Sie ein B * übergeben, muss ich annehmen, dass Sie Verweise oder Zeiger auf Q durch einen Zeiger auf etwas höher in der Hierarchie halten. In diesen Fällen findet die statische Namensauflösung nur DoIt (A *) und da B * ein A * ist, wird es upcasted und das ist die Version, die aufgerufen wird. Da es virtuell ist, wird die Überschreibung in Q aufgerufen.

Wenn Sie einen Zeiger auf Q als Zeiger auf Q hatten und DoIt mit B * aufgerufen wurde, sollte die DoIt (B *) - Funktion aufgerufen werden. An diesem Punkt ist keine doppelte Übermittlung erforderlich und wird nicht verwendet.

Sie benötigen doppelten Versand, wenn Sie zwei abstrakte Typen und eine Funktion haben, die sich basierend auf den konkreten Typen der beiden Abstraktionen unterschiedlich verhalten müssen. Dies versuchen Sie, wenn Sie DoIt mit B auf Q auf einer höheren Ebene als die statische Benennung aufrufen. Es gibt zu viele Methoden, die unterschiedliche Anforderungen erfüllen, um in Ihrem Fall eine Lösung gegenüber einer anderen vorschlagen zu können. Sie wissen nicht wirklich, was Sie zu lösen versuchen. Tatsächlich brauchen Sie es vielleicht nicht einmal! Ein besserer Ansatz für Sie könnte darin bestehen, DoIt (B *) als virtuelle Funktion an der Spitze Ihrer Hierarchie zu implementieren.

Ich würde vorschlagen, dass Sie Andre Alexandrescus Buch, Modern C++ Design, und schauen Sie es sich an. Er erklärt eine ziemlich coole Besucherimplementierung sowie einen skalierbaren Mehrfachversandmechanismus. Hör nicht damit auf, es gibt andere großartige Implementierungen, die die Frage anders beantworten können.

Viel Glück.

+0

Vielen Dank für Ihre Kommentare. In meinem Fall können Sie sich vorstellen, dass A und B Ereignisse sind, P ist ein Ereignisbus und Q ist ein Ereignisprozessor. Es ist also nicht möglich, dass P für jedes mögliche Ereignis ein virtuelles DoI deklariert. – PeeWee2201

0

Was Sie das Verhalten erwarten, jemand zu sein, wenn passiert in einem A * aber der zugrunde liegenden Typ ist wirklich ein B?

Sie suchen, wie dies für etwas werden: jedes Mal, wenn DoIt nennen, zumindest in dem gegebenen Code

class A 
{ 
public: 
    virtual ~A() {} 
    virtual bool isB() const { return false; } 
}; 

class B : public A 
{ 
public: 
    bool isB() const { return true; } 
}; 

void Q::DoIt(A* a) 
{ 
    print(a->isB() ? "true" : "false"); 
} 
1

DoIt(B* b) nie, weil man nie in Objekte vom Typ B * passieren, werden aufgerufen, Sie übergeben Objekte vom Typ A*.

Betrachten Sie die Situation, in der die Überschreibung von Doit(A* a) nicht existierte. Ihr aktueller Code würde nicht kompiliert, da der Compiler ein Objekt vom Typ A* nicht implizit in B* umwandeln kann.

+0

Dies ist fast sicher nicht der Fall. Es wäre ziemlich albern, wenn jemand Objekte vom Typ A * übergibt und erwartet, dass die B * -Version aufgerufen wird. Was sie wahrscheinlich erwarten, ist, dass die B * * - Überladung * tatsächlich * die A * -Version übersteuert und das in C++ nicht funktioniert. –

0

Was Sie versuchen zu tun ist als multiple dispatch bekannt und wird nicht in C++ funktionieren, weil das Überladen der Funktionen statisch ist. Sehen Sie sich den Wikipedia-Artikel für mögliche Arbeitsumgebungen an.

Zum Beispiel, wenn Sie nicht die Logik für die DoIt Funktionalität wollen in den A und B Klassen selbst als virtuelle Funktion dann könnten Sie die dynamic_cast Methode verwenden:

class A { 
}; 

class B : public A { 
}; 

class P : protected std::list<A*> 
{ 
protected: 
virtual void DoIt(A* a) = 0; 
public: 
void WorkerThread() { for (it=begin(); it!=end(); it++) DoIt(*it); } 
}; 

class Q : public P 
{ 
protected: 
void DoIt(A* a) { 
    if(B *b = dynamic_cast<B*>(a)) { 
    // It's a B*, you can "use" b here 
    print("true"); 
    } else { 
    // It's an A* 
    print("false"); 
    } 
} 
}; 
+0

-1 -> doppelter Versand ist in C++ ziemlich üblich. Vielfach auch. Es gibt zahlreiche Ansätze. –

+0

@Noah: Ja, häufig mit verschiedenen Methoden verwendet. Aber nicht nativ von der Sprachspezifikation unterstützt, wie er es versucht. – joshperry

+0

-1 für die Übernahme von einem ** STL Container ** –

0

Sie suchen Mehrfachversand oder Multimethoden. Wikipedia hat ein schönes Beispiel für C++; Verbindung here.

2

Sie suchen nach einem doppelten Versandmechanismus, der nicht in die Sprache integriert ist. Es gibt verschiedene Ansätze, wie dies basierend auf dem Besuchermuster implementiert werden kann. Google für den Doppelversand in C++. Beachten Sie, dass dies ein Patch ist und nicht leicht zu großen Hierarchien erweitert:

struct visitor; 
struct A { 
    virtual void accept(visitor& v) { v(*this); } 
}; 
struct B { 
    virtual void accept(visitor& v) { v(*this); } 
}; 
struct visitor { 
    virtual void operator()(A&) = 0; 
    virtual void operator()(B&) = 0; 
}; 
struct myvisitor : visitor { 
    void operator(A&) { std::cout << "A" << std::endl; } 
    void operator(B&) { std::cout << "B" << std::endl; } 
}; 
int main() { 
    std::vector<A*> data = ... 
    myvisitor v; 
    for (std::vector<A*>::iterator it = data.begin(), end = data.end(); it != end; ++it) 
    { 
     (*it)->accept(v); 
    } 
} 

Der übliche Mechanismus verwendet wird, und accept wird auf den abschließenden Übergehungseinrichtung des Verfahrens versendet werden, was wiederum die Besucher-Methode aufrufen wird. Nun, zu diesem Zeitpunkt ist der statische Typ des Arguments für den Besucher operator() tatsächlich der tatsächliche Typ, mit dem Sie die Funktion aufrufen möchten.

+1

eine Genauigkeit, da das OP bei override/overload nicht klar zu sein scheint, erfordert diese Lösung, dass die Basisklasse ("visitor") virtuelle Methoden für alle konkreten Typen bereitstellt. Man kann nicht von 'visitor' ableiten und eine weitere * overload * von' operator() 'hinzufügen –

+0

Danke. Ich denke, das ist was ich brauche. – PeeWee2201

+0

Danke.Ich denke, das ist was ich brauche. Die Einschränkung, dass die Basis-Besucherklasse jeden Typ (A, B, ...) kennen muss, ist in meinem Fall wahrscheinlich akzeptabel. Auf jeden Fall werde ich andere in diesem Post erwähnte Lösungen überprüfen. – PeeWee2201

Verwandte Themen