2016-12-12 10 views
2

、私はリストを反復するinstrumentsとコールdoPiano() iffカー楽器はピアノでギターの場合はdoGuitar()です。これらの2つの機能は大きく異なり、クラスInstrumentでは抽象的にすることはできません。C++多重ディスパッチ

問題は、C++は実行時にInstrumentの型を識別できないことです(単一ディスパッチのため)。イテレータが指している現在のタイプに応じて、ピアノまたはギター機能を呼び出す方法をどのように達成できますか?

私はsthを実装できたらうれしいです。この擬似コードのような作業:

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

結果

実は、私は私のアプローチにはいくつかの問題に遭遇しました。私はこの投稿を使って多くのリファクタリングを行った:How does one downcast a std::shared_ptr?。あなたの助けを借りてありがとう:

+3

なぜ、単に正しいことをする仮想 'do'関数がないのですか? –

+0

は 'std :: dynamic_pointer_cast'を見ています。その後、あなたのデザインをリッピングして、もう一度やり直してください。多態性は、すべての派生クラスが同じインタフェースを合理的に共有できる場合にのみ適しています。 –

+0

実際、私の例は私の「本当の」実装のほんの一部です。 'Instrument'にはいくつかの純粋な仮想関数があります。私はそれを言及すべきだった。 – Kapa11

答えて

-2

実行時にクラスを識別する1つの方法は、dynamic_castを使用することです。しかし、それを使用するには、あなたのクラスに少なくとも1つの仮想メソッドが必要です。この目的のために、空の仮想メソッドを計測器クラスに追加することができます。

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; 
}; 

対象クラスポインタに対してdynamic_castを実行すると、オブジェクトの種類を確認できます。 dynamic_castは、オブジェクトを目的のクラスにキャストできない場合はNULLを返します。

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

新しい計器が追加されるたびに、すべてのコードを調べ、この新しい計器のケースを手動で追加する必要があります。なぜクラス階層に気をつけますか? – UmNyobe

+0

デストラクタ 'virtual'を作るだけではどうですか?結局のところ、あなたはそれが必要になるベースポインタで格納しているので。 – Sean

+0

@UmNyobe:OPは "これらの2つの機能が大きく異なるため、Instrumentクラスで抽象的にすることはできません"と述べています。それが私がダイナミックなキャストを提案した理由です。 –

3

デザインは、おそらく、この問題を解消するために改善することができますが、既存の設計で作業するとは、多型引数としてPlayerを取る仮想メンバ関数Instrument::play_itを追加することができます。 Playerには、play_guitar(ギターの引数を取る)とplay_piano(ピアノの引数を取る)の2つの機能があります。ギタークラスでは、play_itをオーバーライドして、自己を引数としてPlayer::play_guitarを呼び出します。ピアノのクラスでplay_itをオーバーライドして、Player::play_pianoをselfを引数として呼び出します。ママにはキャストはありません。

これは正確に複数のディスパッチではなく、ビジターパターンと呼ばれています。しかし、あまり重点を置かないようにするのが一番です。visitorという名前をつけたり、記述的ではない愚かな名前を付けたりしないようにしましょう。

0

ダブルディスパッチは、この(擬似コード、省略重要しかし些細なもの)のように動作します:

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); 
}; 

今、私たちは具体的な訪問者を考案することができます。あなたも、共通の基本クラスを必要としない。このため

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

playInstrument[0](); 

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

なぜポインタですか?これはGoogleではありません。 It's: –

+0

@ Cheersandhth.-Alf時には本当に混乱する可能性のある '* this'を書く必要がないため。しかし、それは味の問題です。参照やポインタを使用して、私は反対しません。 –

0

タイプの消去は別のオプションです。