2012-11-29 3 views
9

は通常のインタフェースとコールバックを実装しています(大まかな例)インターフェイスまたは関数オブジェクトによるコールバック? OO、一

class Message {} 

class IMsgProcessor { 
public: 
    virtual void handle_msg(const Message& msg) = 0; 
} 

class RequestMsgProcessor : public IMsgProcessor { 
    virtual void handle_msg(const Message& msg) { 
    // process request message 
    } 
} 

class CustomSocket { 
public: 
    Socket(IMsgProcessor* p) : processor_(p) {} 

    void receive_message_from_network(const Message& msg) { 
     // processor_ does implement handle_msg. Otherwise a compile time error. 
     // So we've got a safe design. 
     processor_->handle_msg(msg); 
    } 
private: 
    IMsgProcessor* processor_; 
} 

これまでのところは良いです。 C++ 11では、これを行う別の方法として、CustomSocketに のstd :: functionオブジェクトのインスタンスを受け取るだけです。それが実施される場合、それは気にしませんか オブジェクトがnull以外の値であったとしても:
1.どのようなパフォーマンスへの影響について:今ここに

class CustomSocket { 
public: 
    Socket(std::function<void(const Message&)>&& f) : func_(std:forward(f)) {} 

    void receive_message_from_network(const Message& msg) { 
     // unfortunately we have to do this check for every msg. 
     // or maybe not ... 
     if(func_) 
      func_(msg); 
    } 
private: 
    std::function<void(const Message&)> func_; 
} 

が質問ありますか?私は、仮想関数呼び出しは、関数オブジェクトを呼び出すよりも速いが、どれぐらい高速であると推測していますか?私は高速のメッセージングシステムを実装しており、むしろ不必要なパフォーマンス上のペナルティを避けたいと考えています。
2.ソフトウェアエンジニアリングの面では、私は第2のアプローチがより好きだと言わなければなりません。少ないコード、少ないファイル、少ないクラッタ:インターフェイスクラスなし。柔軟性の向上:関数オブジェクトの一部を設定し、他の部分をnullのままにして、インタフェースのサブセットのみを実装することができます。あるいは、別々のクラスで実装されたインタフェースの異なる部分を持つことも、フリー関数または両方の組み合わせで実装することもできます(単一のサブクラスではなく)。さらに、CustomSocketは、IMsgProcessorのサブクラスだけでなく、どのクラスでも使用できます。私の意見では、これは大きな利点です。
あなたはどう思いますか?これらの議論に根本的な欠陥がありますか?

+0

と同じ効率を持つ '//残念ながら、私たちはすべてのmsg.'ためには、このチェックをしなければならない - 同じことは、インタフェースコードのために真でなければなりません。あなたはnullポインタを渡さないと決してチェックしません... – Xeo

+0

perfsについて:virtual funcは基本的にテーブル参照を追加します。std :: functionは特殊なアダプタに呼び出しを転送します。あなたの関数/メソッドが仮想でない場合、それは速くなるように最適化されるかもしれません。ベンチマークする必要があります。 'std :: function'をテンプレート引数に置き換えると、仮想呼び出しよりも速くすることができます。 – Antoine

+0

@ Xeo必ずしもそうではありません。インターフェイスコードでは、渡されたオブジェクトがNULLでないことをコンストラクタでチェックします。NULLを受け入れることは意味をなさない。 しかし、2番目のケースでは、インタフェースの部分実装を行うことができるため(つまり、一部の関数オブジェクトのみがnullでない可能性があるため)、チェックが必要です。 – PoP

答えて

3

は、あなたはそれがさらに簡単にソケットに別のコンストラクタを追加するために

template<class F> 
class MsgProcessorT:public IMsgProcessor{ 
    F f_; 
    public: 
    MsgProcessorT(F f):f_(f){} 
    virtual void handle_msg(const Message& msg) { 
     f_(msg); 
} 

}; 
template<class F> 
IMsgProcessor* CreateMessageProcessor(F f){ 
    return new MsgProcessor<T>(f); 

}; 

が次にあなたがまたはこの

Socket s(CreateMessageProcessor([](const Message& msg){...})); 

ように使用するか、両方の長所を持つことができます

class Socket{ 
... 
template<class F> 
Socket(F f):processor_(CreateMessageProcessor(f){} 


}; 

次にできること

Socket s([](const Message& msg){...}); 

そして、まだ、仮想関数呼び出し

+4

Ew、naked 'new'!あなたの恥を隠す! ...もっと重大なことに、 'std :: function'は組み込みの仮想ディスパッチか、関数ポインタ、void *と関数テンプレートを使った手動実装を内部的に使います。 – Xeo

+0

@Xeoあなたは正しいですか?私はSocketをコピー不可能にし、processor_にunique_ptrを使用し、CreateMessageProcessorからunique_ptrを返します。 –

+0

@尾:ハハ、それは私をひっくり返した。 : - ] – ildjarn

2

インターフェイスのアプローチはより伝統的ですが、より冗長でもあります。MessageProcessorが何をしているかがわかり、確認する必要はありません。さらに、多くのソケットで1つの同じオブジェクトを再利用することができます。

std::functionのアプローチがより一般的です。operator()(Message const&)を受け入れるものはすべて使用できます。しかし、冗長性の欠如は、コードを読みにくくする危険性があります。

私はパフォーマンスのペナルティについてはわかりませんが、大きな違いがある場合は驚くでしょう。

コードの必須部分(これはそうであると思われる)の場合は、私はインターフェイスのアプローチに固執します。

1

両方の例では、実際にインターフェイスを使用しています。異なるのは、あなたがそれらを定義する方法です。最初のケースでは、インターフェイスは純粋な仮想関数を持つ伝統的なクラスであり、2番目のケースではインターフェイスは関数参照です。これは設計の観点からC関数ポインタとは大きく異なります。私の意見では、特定の要件に応じて両方のバリエーションを混在させることができ、新しいケースごとに賛否両論(あなたが述べたようなもの)を検討することができます。パフォーマンスへの影響については、テストを実行し、結果を比較し、パフォーマンス要件に一致することが最善の答えだと思います。

関連する問題