c – 通过抽象模板基类接口指针访问派生类方法,在接口中没有显式类型

这是我的第一篇文章.我花了几个小时检查我的问题的解决方案,搜索链接后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
};

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

我不想重新声明Push和operator [](或任何其他使用模板作为参数的函数)来实现我的所有类特化.更确切地说,如果我想添加,让我们说,

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

    };

我怎样才能避免这些重新声明?我需要一些“虚函数模板”,但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相关的返回类型.我怎样才能解决这个问题,让我的经理真正变成多态?

最佳答案
首先,让我们回顾一下您的要求:

>有一个非模板化的多态基类,IList
>有一个类模板,Queue< T>从基类继承来实现它.
>能够专门化Queue< T>随你怎么便
>据推测,如果你从推动中恢复无效,那么const T&来自operator [],您希望通过异常发出错误信号.
>将特定类型的参数传递给基类IList,并且得到的行为取决于Queue< T>的基础类型是否为基础类型.匹配给定参数的类型.

最后一点是关键:您正在尝试根据调用者的运行时类型和参数的静态类型来选择函数的行为.但是,哪种类型实际上与实现Queue< T>中的T匹配.在运行时确定.

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

我通过this answer了解了与当前问题的相似之处,它提供了一系列精彩的链接,以获取有关在C中实现(和实现)完整多方法功能的更多细节.

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

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

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

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

话虽如此,让我们来看看代码.对于由各种Child< T>实现的接口Parent,您可以使用它来从Child< T>获得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";
    }
}

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

i: 3
bad cast caught

这就是我们想要的.

一旦你在这里提供了简单的多态接口,实现你的集合应该很容易.我将使用围绕std :: vector< std :: unique_ptr< Parent>>的包装类.我自己,但这个决定最终取决于你.

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

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

The basic algorithm is the compiler will start at the type of the current value and proceed up the hierarchy until it finds a member on the type which has the target name. It will then do overload resolution on only the members of that type with the given name. It does not consider members of the same name on parent types.

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

转载注明原文:c – 通过抽象模板基类接口指针访问派生类方法,在接口中没有显式类型 - 代码日志