2016-09-16 4 views
3

私はC++を学んでおり、組み込みのC++プロジェクトで割り込みを処理するためにC#イベントと同様のものを構築したいと考えています。C++多型:私は何が欠けていますか?

これまでのところ、私が望むほとんどのことを行うソリューションを考え出しました。しかし、私は多形(?)にいくつかの助けが必要です。次のコードスニペットは、私の状況を再現するために最低限の例のようなものです:

#include <iostream>  

struct Event 
    { }; 

struct EventHandler 
    { 
    virtual void Esr (const Event& I) { } 
    }; 

struct EventSender 
    { 
    EventSender (EventHandler& Handler) : _Handler (Handler) { } 

    template <typename T> 
    void SendEvent (const T&) const 
     { 
     _Handler.Esr (T()); 
     } 

    EventHandler& _Handler; 
    }; 

struct SpecialEvent : public Event 
    { }; 

struct MyHandler : public EventHandler 
    { 
    void Esr (const Event& I) override { std::cout << "Event" << std::endl; } 
    void Esr (const SpecialEvent& I) { std::cout << "SpecialEvent" << std::endl; } 
    };    

int main() 
    { 
    MyHandler handler; 
    EventSender sender (handler); 

    /* Invoke directly */ 
    handler.Esr (Event()); 
    handler.Esr (SpecialEvent()); 

    /* Invoke indirectly */ 
    sender.SendEvent (Event()); 
    sender.SendEvent (SpecialEvent()); // Expected cout msg: "SpecialEvent" 

    return 0; 
    } 

予想されるコンソール出力:

Event 
SpecialEvent 
Event 
SpecialEvent 

実際のコンソール出力:コンパイラ/リンカを何

Event 
SpecialEvent 
Event 
Event 

ここで私は認識していないのですか?

+0

ないことが問題に関連しているかどうかわからを、しかし、なぜあなたは 'SendEvent'にイベントインスタンスを渡しますし、 'SendEvent'の中でこのパラメータは無視されますが、新しい' Event'インスタンスが 'Esr'に渡されますか? – user463035818

+0

このような問題を解決する適切なツールは、デバッガです。スタックオーバーフローを尋ねる前に、コードを一行ずつ進める必要があります。詳しいヘルプは、[小さなプログラムをデバッグする方法(Eric Lippert)](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/)を参照してください。最低限、問題を再現する[最小、完全、および検証可能](http://stackoverflow.com/help/mcve)の例と、その問題を再現するためのデバッガ。 –

+0

興味深い質問:答えに向かっての一歩だが、完全な答えではないので、コメント:{} is = 0;を置き換えることでEsrメソッドを純粋仮想にする。メソッド宣言で宣言します。 –

答えて

2

MyHandlerには2つのメソッドがあります。そのうちの1つが基本クラスのメソッドをオーバーライドします もう1つはありません。

一つの解決策は、基本クラスの両方のメソッドを宣言することであろう。

struct EventHandler 
{ 
    virtual void Esr (const Event& I) = 0; 
    virtual void Esr (const SpecialEvent& I) = 0; 
}; 

その方法コンパイラはEventHandlerのレベルでメソッドを解決するために、引数の型を使用することができます。コンパイラ/リンカを何

struct EventHandler 
{ 
    virtual void Esr (const Event& I) = 0; 
    virtual void Esr (const SpecialEvent& I) 
    { 
     // if not overridden, use the non-specialized event handler. 
     Esr(reinterpret_cast<const Event &>(I)); 
    } 
}; 

は、あなたの質問に答えるために:あなたはすべての派生クラスは、あなたがこのような何かを行うことができ、両方のメソッドをオーバーロードしなければならないという要件を避けたかった場合

ここで私は認識していないのですか? Cにおいて

++メソッド呼び出しは、コードの特定のブロックに1)呼び出し(メソッド本体)のいずれかにコンパイル/リンク時に解決、又は2)隠れデータ構造を介した間接的な呼び出しが仮想テーブルと呼ばれ。実際のvtableは実行時に決定されますが、コンパイラは呼び出しに使用するテーブルのどのエントリを決定する必要があります。 (彼らが何であり、どのように実装されているかについての詳細は、Googleのvtableを参照してください)

これは、知ることができる範囲で決める必要があります。この場合、メソッドが呼び出されるポインタまたは参照の型に基づいています。これは必ずしも実際のオブジェクトの型ではないことに注意してください。あなたのケースでは

あなたはコンパイラがそのようにそれはあなたが期待するものを選ぶことができますMyHandlerで宣言された両方の方法について知ることができるが、コールがsenderを通過するとき、それはEventSenderで宣言された方法を見つける必要がありますされhandler throgh呼び出します。 EventSenderに宣言されているメソッドは1つだけです。幸いにも、引数はconst Event &に強制されるので、コンパイラはそのメソッドを使用できます。したがって、そのメソッドのvtableエントリを使用します。だから、[実行時] MyHandlerためのvtableを見つけ、あなたは間違った方法で終わるかだ

Esr (const Event& I) 

ためのvtableのエントリを使用しています。

ご意見:私の回答は、あなたが見ていることを説明し、あなたの直ぐの問題を解決する方法を提供することを目的としています。 Jerry Coffin氏の答えは、長期的にはあなたのためにはうまくいくはずの代替アプローチを提供します。

1

まず、基本クラスの子孫への参照をキャストすることはできません。 そのタイプへのポインタを使用し、dynamic_castを使用する必要があります。

だから、あなたはmain()

EventSender sender (handler); 

を持っています。 senderのコンストラクタは、MyHandlerの基本クラス(EventHandler)にバインドされます。これは、MyHandler(= EventHandler::EventHandler)のコンストラクタのパラメータ型であるためです。したがって、EventHandler.Esr(const Event &)が呼び出されます。これは仮想であるため、MyHandler.Esr(const Event &)へのポインタがあります。

は、技術的には,およびEsr(const SpecialEvent &)であることに注意してください。彼らはちょうど同じ名前を使用することが起こります。

3

ここでは、古典的な(仮想関数ベースの)多態性ではなく、オーバーロードを使用しようとしています。

あなたは(少なくとも私が理解しているように)handlerを直接使用し、senderを介して間接的にそれを呼び出すことの間で本質的に同じ動作です。変化は、EventSpecialEventの間です。代わりに、これにより

struct Event { 
    virtual void operator()() const { std::cout << "Event\n"; } 
}; 

struct SpecialEvent : public Event { 
    virtual void operator()() const override { std::cout << "Special Event\n"; } 
}; 

Eventへの参照(またはポインタ)用部材を起動します場合であり、古典的な多型がSpecialEventでオーバーライドいEventで仮想関数を含むであろうこと

実際のタイプ。ここで多型を行うことは、我々は唯一のハンドラクラスを必要があることを意味するので、コードはこのような何か終わる:

#include <iostream> 

struct Event { 
    virtual void operator()() const { std::cout << "Event\n"; } 
}; 

struct EventHandler { 
    void Esr(const Event& I) const { I(); } 
}; 

struct EventSender { 
    template <typename T> 
    void SendEvent (const T& t) const { 
     handler.Esr(t); 
    } 

    EventHandler handler; 
}; 

struct SpecialEvent : public Event { 
    virtual void operator()() const override { std::cout << "Special Event\n"; } 
}; 

int main() { 
    EventHandler handler; 
    EventSender sender; 

    /* Invoke directly */ 
    handler.Esr (Event()); 
    handler.Esr (SpecialEvent()); 

    /* Invoke indirectly */ 
    sender.SendEvent (Event()); 
    sender.SendEvent (SpecialEvent()); // Expected cout msg: "SpecialEvent" 
} 
関連する問題