【问题标题】:Passing type information to function in lieu of virtual template function C++将类型信息传递给函数以代替虚拟模板函数 C++
【发布时间】:2011-02-02 09:22:29
【问题描述】:

我有一个实现以下功能的基类:

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 中实现每个重载的函数并将它们标记为虚拟。

干杯, 格雷姆

【问题讨论】:

  • 这就是虚函数的用途
  • 虚拟函数不是我关心的;它对每个可能的 Msg 类都进行了重载。
  • 你也可以让 msg 多态化——并摆脱模板吗?你有多少个味精课程?
  • @aaa - 你的意思是给所有 Msg 类一个共同的基类吗?

标签: c++ templates virtual overloading


【解决方案1】:

如果你真的不想使用虚函数(这对它们来说似乎是一个完美的用例,但我不知道你的消息类),你可以使用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 成员函数:(这种技术称为 Type Erasure em>)

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 类,并且您拥有一个独特的外观,您可以将其传递给客户端类中的虚拟函数。

如果消息类将非常不同,并且客户端类可能对它们做出不同的反应,并且您将拥有大量消息类,那么这就会开始产生异味。或者也许你有一个丑陋可怕的候选人Visitor pattern

【讨论】:

  • 我认为这行不通,因为在您的示例中,您不能拥有带有“仅”Consumer 对象的容器 ...
  • @Martin:当然。我只是公开了用于委派派生类的虚函数的“标准”替代方案。
  • 这将是一个框架,可以轻松插入额外的 Msg 类。如果我必须为每个 Msg 对象重载一个函数,它可能很快就会超过 100 个。这就是我看到的模板化基本回调函数的好处,它可以捕获客户端中未重载的任何内容,因此允许使用一组新的 Msgs 创建一个新的客户端,而无需更改框架的其余部分。
  • @MartinStettner:哎呀,抱歉,当我注意到这一点时,我确实删除了我的评论。
  • @Graeme:如果client和msg对象只在运行时知道,恐怕你别无选择,只能使用virtual dispatch。
【解决方案2】:

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

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

编辑

就个人而言,我会实现一些包含类型代码的基本消息类,并在客户端类中实现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 设为多态(例如虚拟析构函数)并使用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));
    }
    // ...
 };

【讨论】:

  • 所以你是说没有办法避免为每个 Msg 类编写重载函数?
  • 恐怕,是的。这样想:当使用Consumer 基类时,编译器对您不同的客户端实现一无所知。他怎么可能知道,对于给定的Msg 类型,如果客户端仅将其声明为模板,则它是否实现了重载方法。我会接受@aaa 的建议,使 msg 类成为多态或至少具有某种类型代码。然后你可以在每个客户端类中实现一个 switch 语句,让客户端决定处理哪些消息。
【解决方案3】:

这总是一个难以完全扩展的情况,就像访问者模式通常的情况一样。

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-15
    • 1970-01-01
    • 2016-06-01
    • 1970-01-01
    • 2017-12-25
    • 1970-01-01
    • 2011-12-01
    • 1970-01-01
    相关资源
    最近更新 更多