将类型信息传递给函数代替虚拟模板函数 C++

发布于 2024-10-15 18:54:33 字数 555 浏览 4 评论 0原文

我有一个实现以下内容的基类:

struct Consumer
{
    template <typename T>
    void callback(T msg) { /*null implementation */ }
};

然后我有一个类实现这个:

struct Client : public Consumer
{
     void callback(Msg1 msg);
     void callback(Msg2 msg);
     void callback(Msg3 msg);
};

问题是我有一个被视为 Consumer* 的 Client 对象容器,我想不出一种方法让这些 Consumer 对象调用派生对象功能。我的预期功能是拥有多个客户端,每个客户端都为每个 Msg 类实现一个重载函数,这对它们来说意味着一些东西,其余的调用只需调用基类中的 null 实现

任何想法我如何才能使派生类成为叫?现在我需要实现 Consumer 中的每个重载函数并将它们标记为虚拟。

干杯, 格雷姆

I have a base class which implements the following:

struct Consumer
{
    template <typename T>
    void callback(T msg) { /*null implementation */ }
};

I then have a class implement this:

struct Client : public Consumer
{
     void callback(Msg1 msg);
     void callback(Msg2 msg);
     void callback(Msg3 msg);
};

The issue is I have a container of Client objects treated as Consumer* and I can't think of a way to get these Consumer objects to call the derived functions. My intended functionality is to have multiple Clients each of which implement an overloaded function for each Msg class that means something to them and the rest of the calls simply call the null implementation in the base class

Any thoughts how I can get the derived class to be called? Right now I need to implement every overloaded function in Consumer and mark them as virtual.

Cheers,
Graeme

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

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

发布评论

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

评论(3

浪漫人生路 2024-10-22 18:54:33

如果您真的不想使用虚函数(实际上这似乎是它们的完美用例,但我不知道您的消息类),您可以使用 CRTP

template <typename U>
struct Consumer
{
    template <typename T>
    void callback(T msg)
    { static_cast<U*>(this)->callback(msg); }
};


struct Client : Consumer<Client>
{
     void callback(Msg1 msg);
     void callback(Msg2 msg);
     void callback(Msg3 msg);
};

当然,问题是您无法再在容器中存储 Consumer 对象。由于一切都是编译时的,因此客户端的实际类型必须与消费者对象一起存储,以便编译器调用正确的回调函数。虚函数允许您等到运行时...

是否有理由不让 Msg 类多态并使用标准虚函数(除了“我必须重写所有代码,但我不能”) )?

编辑如果您关心的是消息类,为什么不使用类似的东西,假设消息类实现 DoSomething 成员函数:(这种技术称为类型擦除

struct AnyMsg
{
    template <typename Msg>
    AnyMsg(Msg x) : impl(newImpl(x)) {}

    void DoSomething() { impl->DoSomething(); }

private:
    struct Impl
    {
        virtual ~Impl() {}
        virtual void DoSomething() = 0;
    };

    // Probably better is std::unique_ptr if you have
    // C++0x. Or `boost::scoped_ptr`, but you have to
    // provide copy constructors yourself.
    boost::shared_ptr<Impl> impl;

    template <typename Msg>
    Impl* newImpl(Msg m)
    {
        class C : public Impl
        {
            void DoSomething() { x.DoSomething(); }
            Msg x;

        public:
            C(Msg x) : x(x) {}
        };

        return new C(m);
    }
};

您可以自定义 newImpl 的行为来获得您想要的(例如,如果消息类中没有 DoSomething 成员函数,则默认操作,专门化一些消息类或其他)。这样,您就可以像使用模板解决方案一样实现 Msg 类,并且拥有一个可以传递给客户端类中的虚拟函数的独特外观。

如果消息类将非常不同,并且客户端类可能对它们做出不同的反应,并且您将有很多消息类,那么这就开始变得难闻了。或者也许您有一个丑陋而可怕的访问者模式的候选者。

If you really don't want to use virtual functions (this seems to be a perfect use case for them actually, but I don't know about your message classes), you can use the CRTP:

template <typename U>
struct Consumer
{
    template <typename T>
    void callback(T msg)
    { static_cast<U*>(this)->callback(msg); }
};


struct Client : Consumer<Client>
{
     void callback(Msg1 msg);
     void callback(Msg2 msg);
     void callback(Msg3 msg);
};

The problem, of course, is that you cannot store Consumer objects in a container any more. Since everything is compile time, the actual type of the client must be stored alongside the consumer object for the compiler to call the right callback function. Virtual functions allow you to wait until runtime for this...

Is there a reason not to have Msg classes polymorphic and use standard virtual functions (other than "I have to rewrite all the code and I cannot") ?

EDIT If your concern is about message classes, why not use something like that, assuming message classes implement a DoSomething member function: (this technique is known as Type Erasure)

struct AnyMsg
{
    template <typename Msg>
    AnyMsg(Msg x) : impl(newImpl(x)) {}

    void DoSomething() { impl->DoSomething(); }

private:
    struct Impl
    {
        virtual ~Impl() {}
        virtual void DoSomething() = 0;
    };

    // Probably better is std::unique_ptr if you have
    // C++0x. Or `boost::scoped_ptr`, but you have to
    // provide copy constructors yourself.
    boost::shared_ptr<Impl> impl;

    template <typename Msg>
    Impl* newImpl(Msg m)
    {
        class C : public Impl
        {
            void DoSomething() { x.DoSomething(); }
            Msg x;

        public:
            C(Msg x) : x(x) {}
        };

        return new C(m);
    }
};

You can customize the behavior of newImpl to get what you want (eg. default actions if there is no DoSomething member function in the message class, specialization for some message classes or anything else). This way, you implement Msg classes like you would have done with your template solution, and you have a unique facade that you can pass to the virtual functions in your client classes.

If the Message classes are going to be very different, and client classes may react differently to them, and you are going to have a lot of message classes, this begins to smell. Or perhaps you have a candidate for the ugly and scary Visitor pattern.

握住我的手 2024-10-22 18:54:33

由于您不想使用虚拟方法,因此编译器必须静态地(即在编译时)知道要调用哪个函数。如果容器中有不同的客户端对象,编译器现在可能会知道这一点。所以我认为如果不使用虚拟方法就无法解决你的问题(顺便说一句,这些方法是专门为这种情况设计的......)。

当然,您也可以使用一些 switch 语句来手动派生具体类型,但这既不优雅也不高效(并且您必须对所有可能的客户端类型进行硬编码......)

编辑< /strong>

就个人而言,我会实现一些包含类型代码的基本消息类,并在客户端类中实现 switch 语句来处理不同的消息类型,例如:

struct MsgBase {
   int type;
};

struct Consumer {
   virtual void callback(MsgBase msg) { };
};

struct Client : public Consumer {
    void callback(MsgBase msg) {
        switch (msg.type) {
        case MSGTYPE1:
           callback((Msg1)msg);
           break;
        case MSGTYPE2:
           callback((Msg2)msg);
           break;
        // ...
        }
   }
   void callback(Msg1 msg) { /* ... */ }
   void callback(Msg2 msg) { /* ... */ }
};

您还可以制作 MsgBase code> 多态(例如虚拟析构函数)并使用 typeid 来区分(更优雅但效率稍低......)

struct Client : public Consumer {
    void callback(MsgBase* msg) {
        if (typeid(*msg) == typeof(Msg1))
           callback(static_cast<Msg1*>(msg));
        else if (typeid(*msg) == typeof(Msg2))
           callback(static_cast<Msg2*>(msg));
    }
    // ...
 };

Since you don't want to use virtual methods, the compiler would have to know statically (i.e. at compile time) which function to call. If you have different client objects in your container, there is now way the compiler could possibly know this. So I think there's no solution to your problem without using virtual methods (which are btw. exactly designed for this kind of situations...).

Of course you could alternatively using some switch statements for manually deriving the concrete type, but this is neither elegant nor efficient (and you would have to hardcode all possible client types ...)

EDIT

Personally, I'd implement some base message class containing a type code and implement a switch statement in the client class to handle different message types like:

struct MsgBase {
   int type;
};

struct Consumer {
   virtual void callback(MsgBase msg) { };
};

struct Client : public Consumer {
    void callback(MsgBase msg) {
        switch (msg.type) {
        case MSGTYPE1:
           callback((Msg1)msg);
           break;
        case MSGTYPE2:
           callback((Msg2)msg);
           break;
        // ...
        }
   }
   void callback(Msg1 msg) { /* ... */ }
   void callback(Msg2 msg) { /* ... */ }
};

You could also make MsgBase polymorphic (e.g. virtual destructor) and use typeid to differentiate (more elegant but slightly less efficient ...)

struct Client : public Consumer {
    void callback(MsgBase* msg) {
        if (typeid(*msg) == typeof(Msg1))
           callback(static_cast<Msg1*>(msg));
        else if (typeid(*msg) == typeof(Msg2))
           callback(static_cast<Msg2*>(msg));
    }
    // ...
 };
蓝梦月影 2024-10-22 18:54:33

要实现完全可扩展始终是一个困难的情况,就像访问者模式通常的情况一样。

您最终需要最多 V*T 实现,其中 V 是“访问者”的数量,T 是被访问的类型的数量,并且最终可能不得不使用访问者和类工厂模式的混合。

这里的访客将是您的消费者
类工厂将用于消息类型。

使其完全可扩展的最佳方法是为消息/消费者对创建新的函数“对象”,并进行一些双重调度以确保正确的函数被调用。

在您的情况下,您收到不同的消息,然后将它们提供给可能处理它们的消费者?所以每条消息都应该有一个可识别的“类型”和你的消费者
应该在表中查找此类型以为其创建处理程序。

每个消费者类的每种类型可以有一个处理程序。

This is always a difficult situation to make totally extensible, as is the case usually with the Visitor pattern.

You end up needing up to V*T implementations where V is the number of "visitors" and T is the number of types being visited and will probably end up having to use a mixture of visitor and class factory pattern.

visitors here would be your consumers
class factory would be used for the message types.

and your best way to make it totally extensible is to create new function "objects" for message/consumer pairs and a bit of double-dispatch to ensure the right one gets called.

In your case you have different messages come in and then you give them to your consumers who might handle them? So each message should have an identifiable "type" and your consumer
should look up this type in a table to create a handler for it.

You can have one handler per type per consumer class.

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