在C ++中为抽象类模板创建接口

时间:2010-07-15 00:10:32

标签: c++ inheritance templates pointers interface

我的代码如下。我有一个抽象模板类Foo和两个子类(Foo1和Foo2),它们派生自模板的实例化。我希望在我的程序中使用指向Foo1或Foo2类型的对象的指针,因此我创建了一个接口IFoo。

我的问题是我不确定如何在界面中包含functionB,因为它依赖于模板实例化。甚至可以通过界面使functionB可访问,还是我尝试不可能?

非常感谢你的帮助。

class IFoo {
    public:
        virtual functionA()=0;

};

template<class T>
class Foo : public IFoo{
    public:
        functionA(){ do something; };
        functionB(T arg){ do something; };
};

class Foo1 : public Foo<int>{
...
};

class Foo2 : public Foo<double>{
...
};

5 个答案:

答案 0 :(得分:15)

你实际上是在尝试不可能的事。

问题的核心很简单:virtualtemplate混合不好。

  • template是关于编译时代码生成的。您可以将其视为某种类型感知宏+一些用于元编程的技巧。
  • virtual是关于运行时决定的,这需要一些工作。

virtual通常使用虚拟表实现(想象一下列出方法的表)。需要在编译时知道方法的数量,并在基类中定义。

但是,根据您的要求,我们需要一个无限大小的虚拟表,其中包含我们尚未见过的类型的方法,并且只会在未来几年内定义......遗憾的是不可能。

如果可能的话?

嗯,这没有意义。使用Foo2致电int时会发生什么?它并不意味着它!因此,它打破了Foo2实现IFoo的所有方法的原则。

所以,如果你说出真正的问题会更好,这样我们可以在设计层面而不是技术层面帮助你:)

答案 1 :(得分:6)

最简单的方法是使您的界面模板化。

template <class T>
class IFoo {
    public:
        virtual void functionA()=0;
        virtual void functionB(T arg){ do something; };
};

template<class T>
class Foo : public IFoo<T>{
    public:
        void functionA(){ do something; };
        void functionB(T arg){ do something; };
};

答案 2 :(得分:4)

由于必须事先知道functionB的参数类型,因此您只有一个选择:将其设置为可以包含每个可能的参数的类型。这有时被称为“顶级类型”,并且boost库具有any类型,它与顶级类型非常接近。这是可行的:

#include <boost/any.hpp>
#include <iostream>
using namespace boost;

class IFoo {
    public:
    virtual void functionA()=0;
    virtual void functionB(any arg)=0; //<-can hold almost everything
};

template<class T>
class Foo : public IFoo{
    public:
        void functionA(){  };
        void real_functionB(T arg)
        {
         std::cout << arg << std::endl;
        };
        // call the real functionB with the actual value in arg
        // if there is no T in arg, an exception is thrown!

        virtual void functionB(any arg)
        {
            real_functionB(any_cast<T>(arg));
        }
};

int main()
{
    Foo<int> f_int;
    IFoo &if_int=f_int;

    if_int.functionB(10);

    Foo<double> f_double;
    IFoo &if_double=f_double;
if_int.functionB(10.0);

}

不幸的是,any_cast不知道通常的转化。例如any_cast<double>(any(123))抛出一个异常,因为它甚至没有尝试将整数123转换为double。如果不关心转换,因为无论如何都不可能复制所有转换。因此存在一些限制,但如有必要,可以找到解决方法。

答案 3 :(得分:0)

我认为你不能得到你想要的东西。如果您要实现您的建议,请考虑这一点:如果您有一个指向IFoo实例的指针并且您调用了functionB(),那么您应该给出什么类型参数?根本问题是Foo1::functionBFoo2::functionB具有不同的签名并执行不同的操作。

答案 4 :(得分:0)

通过将IFoo *指针包装在类中并通过非模板化包装类的通用模板函数公开功能,可以实现类似的功能:

#include <assert.h>

// interface class
class IFoo {
public:
    virtual int type() const = 0; // return an identifier for the template parameter
    virtual bool functionA() = 0;
};

// This function returns a unique identifier for each supported T
template <typename T> static int TypeT() { static_assert("not specialized yet"); }
template <> static int TypeT<bool>() { return 0; }
template <> static int TypeT<double>() { return 1; }
//template <> static int TypeT<...>() { ... }

// templated class
template <typename T> class FooT : public IFoo {
public:
    int type() const override { return TypeT<T>(); }

    bool functionA() override { return true; }

    // not in interface
    bool functionB(T arg) { return arg == T(); }
};

// function to create an instance of FooT (could also be static function in FooT)
static IFoo* CreateFooT(int type)
{
    switch (type)
    {
    case 0: return new FooT<bool>();
    case 1: return new FooT<double>();
    //case ...: return new FooT<...>();
    default: return nullptr;
    }
}


// Non-templated wrapper class
class FooWrapper {
private:
    IFoo *pFoo;
public:
    FooWrapper(int type) : pFoo(CreateFooT(type)) { assert(pFoo != nullptr); }
    ~FooWrapper() { delete pFoo; }

    bool functionA() { return pFoo->functionA(); }

    template <typename T> bool functionB(T arg)
    {
        if(pFoo->type() != TypeT<T>())
        {
            assert(pFoo->type() == TypeT<T>());
            return false;
        }
        return static_cast<typename FooT<T>*>(pFoo)->functionB(arg);
    }



    // fun stuff:
    // (const pendants omitted for readability)

    bool changeType(int type)
    {
        delete pFoo;
        pFoo = CreateFooT(type);
        return pFoo != nullptr;
    }

    IFoo* Interface() { return pFoo; }

    IFoo* operator->() { return pFoo; }

    operator IFoo&() { return *pFoo; }

    template <typename T> FooT<T> *InterfaceT()
    {
        if(pFoo->type() != TypeT<T>())
        {
            assert(pFoo->type() == TypeT<T>());
            return nullptr;
        }
        return static_cast<typename FooT<T>*>(pFoo);
    }
};

int main(int argc, char *argv[])
{
    FooWrapper w1(TypeT<bool>());
    FooWrapper w2(TypeT<double>());

    w1.functionA(); // ok
    w2.functionA(); // ok

    w1.functionB(true); // ok
    w1.functionB(0.5); // runtime error!

    w2.functionB(true); // runtime error!
    w2.functionB(0.5); // ok


    // fun stuff
    w2.changeType(TypeT<bool>()); // older changes will be lost
    w2.functionB(true); // -> now ok

    w1.Interface()->functionA();
    w1->functionA();

    IFoo &iref = w1;
    iref.functionA();

    FooT<bool> *ref = w1.InterfaceT<bool>();
    ref->functionB(true);

    return 0;
}

当然您有责任使用正确的类型调用函数,但您可以轻松添加一些错误处理。