C ++で複数ディスパッチを実装するために、タイプベースのルックアップテーブルを作成するにはどうすればよいですか?
-
22-07-2019 - |
質問
「Messageable」から派生したクラスを含むメッセージングシステムを作成しようとしています。関数handleMessage()がオーバーロードされる方法に基づいてメッセージを受信できます。例:
class Messageable {
public:
void takeMessage(Message& message) {
this->dispatchMessage(message);
}
protected:
void bindFunction(std::type_info type, /* Need help here */ func) {
m_handlers[type] = func;
}
void dispatchMessage(Message& message) {
m_handlers[typeid(message)](message);
}
private:
std::map<std::type_info, /*Need help here*/ > m_handlers;
};
class TestMessageable : public Messageable {
public:
TestMessageable() {
this->bindFunction(
typeid(VisualMessage),
void (TestMessageable::*handleMessage)(VisualMessage));
this->bindFunction(
typeid(DanceMessage),
void (TestMessageable::*handleMessage)(DanceMessage));
}
protected:
void handleMessage(VisualMessage visualMessage) {
//Do something here with visualMessage
}
void handleMessage(DanceMessage danceMessage) {
//Do something here with danceMessage
}
};
簡単に言えば、特定のメッセージのRTTI値に基づいて、適切なバージョンのhandleMessageが呼び出されるようにします。
どうすればモノリシックなswitch / caseステートメントを使わずにこれを実装できますか。
解決
Double Dispatchパターンを調べる必要があります。詳細については、こちらをご覧ください。
VisualMessageを次のようなクラスとして実装できるはずです:
class VisualMessage : public Message
{
public:
virtual void dispatch(Messageable & inMessageable)
{
inMessageable.handleMessage(*this);
}
};
そして次のように呼び出します:
Message & vMessage = VisualMessage();
Messageable & tMessageable = TestMessageable();
vMessage.dispatch(tMessageable);
次にTestMessageable :: handleMessage(VisualMessage&amp; visualMessage)を呼び出します
これは、Message :: dispatchがVisualMessageタイプに基づいているためです。次に、VisualMessage :: dispatchがinMessageable.handleMessage(* this)を呼び出すと、* thisポインターのタイプがMessageではなくVisualMessageであるため、正しいhandleMessageを呼び出します。
他のヒント
コードを修正するには:
struct CompareTypeInfo
: std::binary_function<const std::type_info*, const std::type_info*, bool>
{
bool operator()(const std::type_info* a, const std::type_info* b) {
return a->before(*b);
}
};
class Messageable
{
protected:
typedef void (*handlefn)(Messageable *, Message &);
void bindFunction(const std::type_info& type, handlefn func) {
m_handlers[&type] = func;
}
void dispatchMessage(Message& message) {
m_handlers[&typeid(message)](this, message);
}
template <typename S, typename T>
static void handle(Messageable *self, Message &m) {
static_cast<S*>(self)->handleMessage(static_cast<T&>(m));
}
private:
std::map<const std::type_info*, handlefn, CompareTypeInfo> m_handlers;
};
class TestMessageable : public Messageable
{
public:
TestMessageable()
{
this->bindFunction(
typeid(VisualMessage), &Messageable::handle<TestMessageable,VisualMessage>);
this->bindFunction(
typeid(DanceMessage), &Messageable::handle<TestMessageable,DanceMessage>);
}
public:
void handleMessage(VisualMessage visualMessage)
{
//Do something here with visualMessage
}
void handleMessage(DanceMessage danceMessage)
{
//Do something here with danceMessage
}
}
};
これらのstatic_castsは、「追加の安全性」のためにdynamic_castsにすることができます。 (仮想機能が動き回ると仮定)。しかし、デザインは、selfがSへのポインターでなければならないことを意味します。さもないと、mがTを参照する必要があります。mはTを参照する必要があります。そのため、クラスが正しく使用されていればキャストに失敗することはありません。発生した場合にできることはデバッグだけです。
実際、bindFunctionをテンプレートにすることで、冗長性をもう少し減らすことができると思います:
template <typename S, typename T>
void bindFunction(void)
{
m_handlers[&typeid(T)] = handle<S,T>;
}
次に、次のように呼び出します:
this->bindFunction<TestMessageable,VisualMessage>();
それでも、スティーブロウのダブルディスパッチコードが通常好まれている理由はわかります...
これは古い質問ですが、 NUClear ライブラリは、高速でタイプセーフなメッセージの受け渡しを提供するように設計されていますこの質問の元の意図と同様の流れで。
完全開示:私はNUClearの共同開発者の1人です
この場合、 TestMessageable
クラスは NUClear :: Reactor
として次のように実装されます:
#include <NUClear.h>
// TestMessageable.h
class TestMessageable : NUClear::Reactor {
public:
TestMessageable(NUClear::PowerPlant* powerPlant);
private:
};
// TestMessageable.cpp
#include "TestMessageable.h"
TestMessageable::TestMessageable(NUClear::PowerPlant* powerPlant)
: NUClear::Reactor(powerPlant) {
on<Trigger<VisualMessage>>([this](const VisualMessage& message) {
// Do something with VisualMessage here
// On can also take anything that is callable with a const& VisualMessage.
// Messages are sent using emit.
// If you don't have C++14 NUClear provides std::make_unique
auto classifiedData = std::make_unique<ClassifiedVision>(/* stuff */);
emit(std::move(classifieData));
});
on<Trigger<DanceMessage>>([this](const DanceMessage& message) {
// Do something with DanceMessage here.
});
}
このような実装は、 Scott Meyersのより効果的なC ++ にあります。 item-31 は、必要なものです&amp;うまく説明しました。