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.在实际使用这种解决方法之前,我会考虑为什么我要这么做.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


一.C语言中的static关键字 在C语言中,static可以用来修饰局部变量,全局变量以及函数。在不同的情况下static的作用不尽相同。 (1)修饰局部变量 一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存
浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组的一些区别,然而在某些情况下,指针和数组是等同的,下面讨论一下什么时候指针和数组是相同的。C语言标准对此作了说明:规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针; 注:下面几种情况例外 1)数组名作为sizeof的操作数
浅谈C/C++中的指针和数组(一)指针是C/C++的精华,而指针和数组又是一对欢喜冤家,很多时候我们并不能很好的区分指针和数组,对于刚毕业的计算机系的本科生很少有人能够熟练掌握指针以及数组的用法和区别。造成这种原因可能跟现在大学教学以及现在市面上流行的很多C或者C++教程有关,这些教程虽然通俗易懂,
从两个例子分析C语言的声明 在读《C专家编程》一书的第三章时,书中谈到C语言的声明问题,《C专家编程》这本书只有两百多页,却花了一章的内容去阐述这个问题,足以看出这个问题的重要性,要想透彻理解C语言的声明问题仅仅看书是远远不够的,需要平时多实践并大量阅读别人写的代码。下面借鉴《C专家编程》书中的两个
C语言文件操作解析(一)在讨论C语言文件操作之前,先了解一下与文件相关的东西。一.文本文件和二进制文件 文本文件的定义:由若干行字符构成的计算机文件,存在于计算机系统中。文本文件只能存储文件中的有效字符信息,不能存储图像、声音等信息。狭义上的二进制文件则指除开文本文件之外的文件,如图片、DOC文档。
C语言文件操作解析(三) 在前面已经讨论了文件打开操作,下面说一下文件的读写操作。文件的读写操作主要有4种,字符读写、字符串读写、块读写以及格式化读写。一.字符读写 字符读写主要使用两个函数fputc和fgetc,两个函数的原型是: int fputc(int ch,FILE *fp);若写入成功则
浅谈C语言中的位段 位段(bit-field)是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。含有位段的结构体(联合体)称为位段结构。采用位段结构既能够节省空间,又方便于操作。 位段的定义格式为: type [var]:digits 其中type只能为int,unsigned int,s
C语言文件操作解析(五)之EOF解析 在C语言中,有个符号大家都应该很熟悉,那就是EOF(End of File),即文件结束符。但是很多时候对这个理解并不是很清楚,导致在写代码的时候经常出错,特别是在判断文件是否到达文件末尾时,常常出错。1.EOF是什么? 在VC中查看EOF的定义可知: #def
关于VC+ʶ.0中getline函数的一个bug 最近在调试程序时,发现getline函数在VC+ʶ.0和其他编译器上运行结果不一样,比如有如下这段程序:#include &lt;iostream&gt;#include &lt;string&gt;using namespace std;int
C/C++浮点数在内存中的存储方式 任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100。则在Intel CPU架构的系统中,存放方式为 10000100(低地址单元) 00000100(高地址单元),因为Intel CPU
浅析C/C++中的switch/case陷阱 先看下面一段代码: 文件main.cpp#includeusing namespace std;int main(int argc, char *argv[]){ int a =0; switch(a) { case ...
浅谈C/C++中的typedef和#define 在C/C++中,我们平时写程序可能经常会用到typedef关键字和#define宏定义命令,在某些情况下使用它们会达到相同的效果,但是它们是有实质性的区别,一个是C/C++的关键字,一个是C/C++的宏定义命令,typedef用来为一个已有的数据类型
看下面一道面试题:#include&lt;stdio.h&gt;#include&lt;stdlib.h&gt;int main(void) { int a[5]={1,2,3,4,5}; int *ptr=(int *)(&amp;aʱ); printf(&quot;%d,%d&quot;,*(
联合体union 当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union)。在C Programming Language 一书中对于联合体是这么描述的: 1)联合体是一个结构; 2)它的所有成员相对于基地址的偏移量都为0; 3)此结构空间要大到足够容纳最&quot;宽&quo
从一个程序的Bug解析C语言的类型转换 先看下面一段程序,这段程序摘自《C 专家编程》:#include&lt;stdio.h&gt;int array[]={23,34,12,17,204,99,16};#define TOTAL_ELEMENTS (sizeof(array)/sizeof(ar
大端和小端 嵌入式开发者应该对大端和小端很熟悉。在内存单元中数据是以字节为存储单位的,对于多字节数据,在小端模式中,低字节数据存放在低地址单元,而在大端模式中,低字节数据存放在高地址单元。比如一个定义一个short型的变量a,赋值为1,由于short型数据占2字节。在小端模式中,其存放方式为0X40
位运算和sizeof运算符 C语言中提供了一些运算符可以直接操作整数的位,称为位运算,因此位运算中的操作数都必须是整型的。位运算的效率是比较高的,而且位运算运用好的话会达到意想不到的效果。位运算主要有6种:与(&amp;),或(|),取反(~),异或(^),左移(&gt;)。1.位运算中的类型转换位
C语言文件操作解析(四)在文件操作中除了打开操作以及读写操作,还有几种比较常见的操作。下面介绍一下这些操作中涉及到的函数。一.移动位置指针的函数 rewind函数和fseek函数,这两个函数的原型是:void rewind(FILE *fp); 将位置指针移动到文件首 int fseek(FILE
结构体字节对齐 在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排
C语言文件操作解析(二)C语言中对文件进行操作必须首先打开文件,打开文件主要涉及到fopen函数。fopen函数的原型为 FILE* fopen(const char *path,const char *mode) 其中path为文件路径,mode为打开方式 1)对于文件路径,只需注意若未明确给出绝