如何创建基于类型的查找表以在 C++ 中实现多重调度?

发布于 2024-08-13 00:56:30 字数 1330 浏览 11 评论 0原文

我正在尝试创建一个消息系统,其中任何从“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 语句的情况下,如何更好地实现这一点。

I'm attempting to make a messaging system in which any class derived from "Messageable" can receive messages based on how the function handleMessage() is overloaded. For example:

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

In a nutshell I want the correct version of handleMessage to be called based on the RTTI value of any given message.

How can I implement this preferably without some sort of monolithic switch/case statement.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不必了 2024-08-20 00:56:30

您应该研究双重调度模式。请参阅此处的信息。

您应该能够像这样将 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 & VisualMessage)

这是因为 Message::dispatch 将基于 VisualMessage 类型。然后当VisualMessage::dispatch调用inMessageable.handleMessage(*this)时,它会调用正确的handleMessage,因为*this指针的类型是VisualMessage,而不是Message。

You should look into the Double Dispatch pattern. See information here.

You should be able to implement VisualMessage as a class like such:

class VisualMessage : public Message
{
    public:
        virtual void dispatch(Messageable & inMessageable)
        {
            inMessageable.handleMessage(*this);
        }
};

and then call it like this:

Message & vMessage = VisualMessage();
Messageable & tMessageable = TestMessageable();
vMessage.dispatch(tMessageable);

It will then call TestMessageable::handleMessage(VisualMessage & visualMessage)

This is because Message::dispatch will be based on the VisualMessage type. Then when VisualMessage::dispatch calls inMessageable.handleMessage(*this) it will call the right handleMessage because the type of the *this pointer is VisualMessage, not Message.

小瓶盖 2024-08-20 00:56:30

要修复您的代码:

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,因为它的 typeid 已经在dispatchMessage中检查过。因此,如果正确使用类,就不会发生失败的转换,而如果确实发生,您所能做的就是调试。

实际上,我认为您可以通过将bindFunction也设置为模板来减少冗长的内容:

template <typename S, typename T>
void bindFunction(void)
    {
    m_handlers[&typeid(T)] = handle<S,T>;
    }

然后用以下方式调用它:

this->bindFunction<TestMessageable,VisualMessage>();

但是,您仍然可以看到为什么Steve Rowe的双重调度代码通常是首选......

To fix your code:

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

Those static_casts could be dynamic_casts for "extra safety" (assuming there are virtual functions kicking around). But the design means you know self must be a pointer to S, because otherwise it wouldn't have this function registered to it, and you know m must refer to a T, because its typeid has already been checked in dispatchMessage. So a failed cast can't happen if the class is used correctly, and all you can do if it does happen is debug.

Actually I think you could cut down the verbiage a bit more by making bindFunction a template too:

template <typename S, typename T>
void bindFunction(void)
    {
    m_handlers[&typeid(T)] = handle<S,T>;
    }

Then call it with:

this->bindFunction<TestMessageable,VisualMessage>();

But still, you can see why Steve Rowe's double dispatch code is usually preferred...

澉约 2024-08-20 00:56:30

这是一个老问题,但 NUClear 库旨在以类似的方式提供快速且类型安全的消息传递回到这个问题的初衷。

全面披露:我是 NUClear 的共同开发者之一

在这种情况下,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.
    });
}

This is an old question but the NUClear library is designed to provide fast and type-safe message passing in a similar vein to the original intent of this question.

Full Disclosure: I am one of the co-developers of NUClear

In this case the TestMessageable class is implemented as a NUClear::Reactor like so:

#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.
    });
}
蓝天 2024-08-20 00:56:30

您可以在 Scott Meyers 的 More effective C++ 中找到此类实现,并且
项目 - 31 是您想要的很好地解释了。

You will find such kind of implementation in Scott Meyers' More Effective C++ and
item - 31 is what you want & nicely explained.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文