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

时间:2011-02-02 09:22:30

标签: c++ templates virtual overloading

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

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

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

我有什么想法可以调用派生类吗?现在我需要在Consumer中实现每个重载函数,并将它们标记为虚拟。

干杯, 格雷姆

3 个答案:

答案 0 :(得分:3)

如果你真的不想使用虚函数(实际上这似乎是一个完美的用例,但我不知道你的消息类),你可以使用CRTP:< / p>

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

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

如果Message类会有很大的不同,并且客户端类可能对它们做出不同的反应,并且您将要有很多消息类,这就开始闻起来了。或许你有一个丑陋和可怕的候选人Visitor pattern

答案 1 :(得分:1)

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

当然,您也可以使用一些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));
    }
    // ...
 };

答案 2 :(得分:1)

这通常是一个难以完全扩展的情况,通常是访客模式的情况。

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

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

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

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

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