2016-12-12 2 views
2

Gegeben folgendes Problem:C++ Multiple Dispatch-

class Instrument { 
}; 

class Guitar : public Instrument { 
    public: 
    void doGuitar() const; 
}; 

class Piano : public Instrument { 
    public: 
    void doPiano() const; 
}; 

habe ich eine Liste von Zeigern auf Instrument

list<shared_ptr<Instrument>> instruments; 

, in denen ich Instrumente über (zum Beispiel) hinzufügen

Guitar myGuitar; 
instruments.push_back(make_shared<Guitar>(myGuitar)); 

Jetzt , Ich möchte über die Liste instruments iterieren und doPiano() wenn der curr aufrufen Ent Instrument ist ein Klavier und doGuitar() wenn es eine Gitarre ist. Diese beiden Funktionen unterscheiden sich sehr und können daher in der Klasse Instrument nicht abstrakt gemacht werden.

Das Problem ist, dass C++ den Typ der Instrument nicht zur Laufzeit identifizieren kann, nicht wahr (wegen Einzelversand)? Wie erreiche ich, dass es die Klavier- oder die Gitarrenfunktion in Abhängigkeit vom aktuellen Typ, auf den der Iterator zeigt, aufruft.

Ich würde mich freuen, wenn ich etw. Arbeiten wie diese Pseudo-Code:

list<shared_ptr<Instrument>>::const_iterator it; 
if ("current type == Guitar") 
    (*it)->doGuitar(); 
else if ("current type == Piano") 
    (*it)->doPiano(); 

Ergebnis

Eigentlich lief ich in mehrere Probleme mit meinem Ansatz. Ich habe mit diesem Post viel Refactoring gemacht: How does one downcast a std::shared_ptr?. Vielen Dank an alle für Ihre Hilfe :)

+3

Warum nicht einfach eine virtuelle 'do'-Funktion haben, die das Richtige tut? –

+0

werfen Sie einen Blick auf 'std :: dynamic_pointer_cast'. Dann reiß dein Design auf und fang neu an. Polymorphismus ist nur dann geeignet, wenn alle abgeleiteten Klassen vernünftigerweise die gleiche Schnittstelle teilen können. –

+0

Eigentlich ist mein Beispiel nur ein kleiner Teil meiner "echten" Implementierung. Es gibt mehrere rein virtuelle Funktionen in 'Instrument'. Ich hätte es erwähnen sollen. – Kapa11

Antwort

-2

Eine Möglichkeit zur Identifizierung von Klassen zur Laufzeit ist die Verwendung von dynamic_cast. Um das zu nutzen, müssen Sie mindestens eine virtuelle Methode in Ihrer Klasse haben. Zu diesem Zweck kann der Instrumentenklasse eine leere virtuelle Methode hinzugefügt werden.

class Instrument { 
    private: 
    virtual void emptyMethod_doNotCall() {} // Add this method. 
}; 

class Guitar : public Instrument { 
    public: 
    void doGuitar() const; 
}; 

class Piano : public Instrument { 
    public: 
    void doPiano() const; 
}; 

Der Objekttyp kann, indem Sie eine dynamic_cast zu der Zielklasse Zeiger überprüft werden. dynamic_cast gibt NULL zurück, wenn das Objekt nicht in die gewünschte Zielklasse umgewandelt werden kann.

list<shared_ptr<Instrument>>::const_iterator it; 
if (dynamic_cast<Guitar*>(it) != NULL) 
    (*it)->doGuitar(); 
else if (dynamic_cast<Piano*>(it) != NULL) 
    (*it)->doPiano(); 
+0

Jedes Mal, wenn ein neues Instrument hinzugefügt wird, müssen Sie den gesamten Code durchgehen, den Fall dieses neuen Instruments manuell hinzufügen ... warum sollten Sie sich dann mit einer Klassenhierarchie befassen? – UmNyobe

+0

Warum sollte man den Destruktor nicht einfach virtuell machen? Schließlich müssen Sie, da Sie mit dem Basiszeiger speichern. – Sean

+0

@UmNyobe: Das OP sagt "Diese beiden Funktionen unterscheiden sich sehr und können daher in der Klasse Instrument nicht abstrakt gemacht werden". Das ist der Grund, warum ich eine dynamische Besetzung vorgeschlagen habe. –

3

Der Entwurf kann wahrscheinlich verbessert werden, um dieses Problem zu beseitigen, aber die Arbeit im Rahmen des bestehenden Design können Sie eine virtuelle Memberfunktion Instrument::play_it hinzufügen, die ein Player als polymorphes Argument. In Player haben zwei Funktionen play_guitar (unter Gitarrenargument) und play_piano (Piano-Argument). In der Gitarrenklasse überschreiben Sie play_it, um Player::play_guitar mit Selbst als Argument zu benennen. In der Klavierklasse überschreiben Sie play_it, um Player::play_piano mit self als Argument aufzurufen. Schau ma keine Gussstücke.

Dies ist nicht genau mehrere Versand, es ist bekannt als das Besucher-Muster. Aber es ist vielleicht am besten, sich nicht zu sehr darauf zu konzentrieren, damit Sie nicht beginnen, die Dinge visitor oder solche nicht-beschreibende Torheit zu nennen.

0

Eine Doppel-Ausgabe funktioniert wie diese (Pseudo-Code, wichtig, aber trivial Sachen weggelassen):

struct InstrumentVisitor{ 
    // knows all instruments 
    virtual void doGuitar(Guitar*) = 0; 
    virtual void doPiano(Piano*) = 0; 
}; 

class Instrument { 
    virtual void doInstrument(InstrumentVisitor*) = 0; 
    ... 
}; 

class Piano : public Instrument { 
    void doInstrument (InstrumentVisitor* v) { 
     v->doPiano(this); 
}; 

class Guitar : public Instrument { 
    void doInstrument (InstrumentVisitor* v) { 
     v->doGuitar(this); 
}; 

Jetzt können wir konkrete Besucher entwickeln.

struct Player : InstrumentVisitor { 
    // does vastly different things for guitar and piano 
    void doGuitar (Guitar* g) { 
     g->Strum(pick, A6); 
    } 
    void doPiano (Piano* p) { 
     p->Scale (Am, ascending); 
}; 
+1

Warum die Zeiger? Das ist nicht Google. Es ist SO: –

+0

@ Cheersandhth.-Alf Für nicht zu schreiben "* this", die manchmal sehr verwirrend sein kann. Aber es ist Geschmackssache. Verwenden Sie Verweise oder Zeiger, ich werde nicht widersprechen. –

0

Typ Löschung ist eine weitere Option:

std::vector<std::function<void()>> playInstrument; 
playInstrument.emplace_back([g = Guitar{}]() { return g.doGuitar(); }); 
playInstrument.emplace_back([p = Piano{} ]() { return p.doPiano(); }); 

playInstrument[0](); 

Dazu Sie auch keine gemeinsame Basisklasse benötigen.