c ++ - 通过抽象模板基类接口指针访问派生类方法,在接口

时间:2016-01-26 20:55:50

标签: c++ templates interface

这是我的第一篇文章。我花了几个小时检查我的问题的解决方案,在SO上链接后搜索链接,但没有一个完全描述我的问题(我能得到的最接近的是thisthis)。所以,让我们开始工作吧!

描述:我必须实现一组专门的类,每个类都能够存储其类型的链表。另外(棘手的部分),我必须实现一个集合管理器,以便在集合中添加更多专用类不会影响其代码。

让我解释一下到目前为止。

class IList {
    public:
    virtual IList& operator+( IList&) = 0;
    virtual void print() = 0;
    virtual int g_Size() const = 0;
//perfect till here

    virtual void Push(const int&) = 0;//needs to be upgraded
    virtual const int& operator[](int index) = 0;//needs to be upgraded
};

template<class T>
class Queue: public IList{
    //internal stuff
public:
    Queue();
    int g_Size() const;

    void print();

    void Push(const T& cv);

    const T& operator[](int index);

    ~Queue();
};//all implementation of Queue<T> is implemented and working, but removed for simplicity

class CIntList : public Queue<int>{
    //stuff here, specialized on int
};

class C_Manager{
    IList * classes[3];//notice the polymorphism, managing the class collection using a pointer to the common(base) interface
public:
    void testing()
    {
        for (int i = 0; i < 3; i++)
            classes[i] = new CIntList(i);
        classes[0]->Push(1); classes[0]->Push(2); classes[1]->Push(1121); classes[2]->Push(12);
        classes[0]->print();
        classes[2]->print();
        int a = classes[0]->operator[](1);
        classes[1]->Push(a + a);
    } //working fine
};

好的,所以你可能会问,问题是什么

我不想将Pushoperator[](或任何其他使用模板作为参数的函数)重新声明为我的所有类专精。更确切地说,如果我想添加,请说,

class CFloatList: public Queue<float>
{
      //float stuff goes here
};

我还必须将IList修改为

class IList {
        public:
        virtual IList& operator+( IList&) = 0;
        virtual void print() = 0;
        virtual int g_Size() const = 0;
    //perfect till here

        virtual void Push(const int&) = 0;//used for int
        virtual const int& operator[](int index) = 0;//used for int

    //NEW DECLARATION FOR FLOAT
        virtual void Push(const float&) = 0;//used for float
        virtual const float& operator[](int index) = 0;//used for float

    };

我如何避免这些重新声明?我需要一些&#34;虚拟功能模板&#34;,但这在C ++中不受支持。

我的方法有误吗?

很抱歉没有突出显示c ++语法,这是我的第一篇文章,我只是设法在代码块中格式化它。 感谢您的时间!

编辑#1 更好的解决方案(由jaggedSpire建议 - 很多,非常感谢)

我已将IList修改为

class IList {
    public:
    virtual IList& operator+( IList&) = 0;
    virtual void afis() = 0;
    virtual int g_Size() const = 0;


    //templates
    template<typename T>
    void Push(const T& arg) //WORKS PERFECTLY
    {
        Queue<T>* cast = dynamic_cast<Queue<T>*>(this);
        cast->Push(arg);
    }
    template<typename T>
    const T& operator[](int index) //error
    {
        Queue<T>* cast = dynamic_cast<Queue<T>*>(this);
        return cast->operator[](index);
    }
};

void C_Manager::testing()

class C_Manager{
    public:
        void testing()
        {
            IList * a = new CIntList(1);
            a->Push(200);//WORKS PERFECTLY
            int c = a->operator[](0); //ERROR
        }
    };

并产生这些错误

Error   C2783   'const T &IList::operator [](int)': could not deduce template argument for 'T'

Error   C2672   'IList::operator []': no matching overloaded function found

intellisense: no instance of function template "IList::operator[]" matches the argument list

基本上,它抱怨每个可能模板化函数具有 T相关的返回类型。我怎样才能解决这个问题,让我的经理真正变成多态?

1 个答案:

答案 0 :(得分:1)

首先,让我们回顾一下您的要求:

  • 拥有非模板化的多态基类IList
  • 有一个类模板,Queue<T>继承自基类来实现它。
  • 能够根据您的需要专注Queue<T>
  • 据推测,如果您从void返回push,并从const T&返回operator[],则需要发出异常信号。
  • 将特定类型的参数传递给基类IList,并且结果行为取决于基础类型Queue<T>是否与给定参数的类型匹配。 / strong>

最后一位是关键:你试图根据调用者的运行时类型和参数的静态类型来选择函数的行为。但是,在实现T中实际匹配Queue<T>类型是在运行时确定的。

运行时基于两个对象的运行时类型确定行为(因为参数在运行时以及编译时已知)是多方法的用途。 C ++没有本机多方法支持,但它可以与dynamic_cast

拼接在一起

我通过this answer了解了与当前问题的相似之处,它为C ++中实现(和实现)完整多方法功能提供了一系列精彩的链接。

现在,C ++中多方法的强力/天真实现需要从实现类型列表中测试每种可能的实现类型的参数。这是你也表示你不想要的东西,但不要担心:你不需要。这是因为我们只想测试一种情况,而不是典型的多方法情况所需的许多情况。我们将在编译时添加参数的类型,以便我们可以方便地使用该信息来查找我们感兴趣的唯一目标类型的类型。

对于提供的T类型,我们要测试我们调度的类型是否 Queue<T>

为此,我们将使用更简单的多方法实现中使用的相同测试:dynamic_cast。具体来说,我们将把this指针强制转换为我们正在测试的类型,使用提供的参数类型作为所需模板参数的源。

警告:这意味着如果没有显式模板参数,类型之间的隐式转换将不会发生。如果将字符串文字传递给std::string容器并且没有明确指定您想要一个std::string容器,那么它将查找一个容器,该容器包含字符串文字长度的字符数组,并且没有检测到。毕竟,它们是不同的类型。

话虽如此,让我们来看看代码。对于由各种Parent实施的接口Child<T>,您可以使用此TChild<T>获取Parent特定行为,只能通过class Parent{ public: template <typename T> void foo(const T& t); virtual ~Parent(){} }; template <typename T> class Child : public Parent{ public: void foo(const T& t); }; // must be after the definition of the Child template, // because dynamic_cast requires a complete type to target template <typename T> void Parent::foo(const T& t){ // throws on bad conversion like we want auto castThis = dynamic_cast<Child<T>&>(*this); // if execution reaches this point, this is a Child<T> castThis.foo(t); } 访问template<typename T> void Child<T>::foo(const T& t){ std::cout << typeid(T).name() << ": " << t << '\n'; } int main(){ Parent&& handle = Child<int>(); try{ handle.foo<int>(3); handle.foo<char>(0); handle.foo<std::string>("Hello!"); } catch(std::bad_cast e){ std::cout << "bad cast caught\n"; } } 接口:

i: 3
bad cast caught

使用:

std::vector<std::unique_ptr<Parent>>

我们得到以下输出on both g++ 5.2.0 and clang 3.7

dynamic_cast

这就是我们想要的。

一旦你有了这里提供的简单多态接口,实现你的集合应该很容易。我会自己选择一个std::bad_cast的包装类,但这个决定最终取决于你。

现在,因为这还不够文本墙,所以有些注意事项:

  1. 抛出异常对标准控制流程不利。如果您实际上并不知道参数是否通过某些外部逻辑与基础类型匹配,那么您需要一些其他形式的错误处理。 foo可用于转换引用和指针。对不属于目标类型的对象进行引用将抛出Child<T>。转换指针将返回空指针。
  2. 对派生类中的成员函数使用相同的名称作为模板化成员函数调用基类中的成员函数,因为name lookup在C ++中的工作方式。来自this answer
      

    基本算法是编译器将从当前值的类型开始,然后继续执行层次结构,直到找到具有目标名称的类型的成员。然后,它将仅对具有给定名称的该类型的成员执行重载解析。它不考虑父类型上的同名成员。

  3. 因此Child<T>的查找将从Parent开始,因为它在{{1}}内找到了具有该名称的成员函数,所以它不会检查{{1}}或调用再次调度功能。
     3.在实际使用这种解决方法之前,我考虑为什么我要这么做。