其他分享
首页 > 其他分享> > 含有虚函数的类模板 vs 虚函数模板

含有虚函数的类模板 vs 虚函数模板

作者:互联网

转载自https://my.oschina.net/seanx/blog/716618,

关于模板类写得比较透彻

首先,我们要区分模板类虚函数和模板虚函数,话不多说,先上代码:

模板类虚函数

template<class T>
class A
{
public:
	virtual ~A(){}
	virtual void foo(T &t){}
};

template<class T, class R>
class B : public A<T>
{
	R *r;
public:
	void foo(T &t) override{}
};

模板虚函数

class A
{
public:
	virtual ~A(){}
	template<class T>
	virtual void foo(T &t){}
};

class B : public A
{
public:
	template<class T>
	void foo(T &t) override{}
};

显然模板虚函数是编译不过的,至于为什么,我们可以深究至C++多态的实现原理,就能知道为什么C++不允许定义模板虚函数了。

我们知道C++的多态是通过虚表实现的,对于含有虚函数的类,会为其定义一个虚表,每个实例化的对象都有一个指向该虚表的指针,所以同样的类,含有虚函数的类的实例大小比不含虚函数的多上一个指针的大小,虚表里为每个虚函数维护着一条跳转记录,这些跳转地址在编译期就被确定了,存放在类定义模块的数据段中,在程序运行期是不可修改的。那么这跟模板虚函数有什么关系呢?

让我们了解一点关于模板的特性,C++对于模版的处理,首先,模版并不算一种类型,在编译时,编译器只对已经实例化的模板类生成对应的模板类代码,假如这些类中定义的有模板类虚函数,则对每个实例化的模板类型创建一个虚表,这就是第一种情况---模板类虚函数,是可行的。

现在再看看模板虚函数,为什么不可行,就拿上面的代码讲:

A是一个类型,它含有模板虚函数,虽然是虚函数,但是函数的符号并不确定,因为我们不知道模板T是一个什么类型,对于从没调用过这个模板函数的情况下,这个模板虚函数甚至都不会实例化,那么就相当于没有虚函数了。那么为了实现模板虚函数,我们姑且认为它就是含有虚函数,所以A应该有一张虚表,但是A的虚函数符号并不确定,要根据当前调用的情况来确定,A的这个模板虚函数到底实例化了几个类型,那么对于每个类型的虚函数都添加一个虚表记录,这样看起来,实现模板虚函数貌似是可行的,但是这也只仅限于单个文件编译成可执行文件的情况下。

我们都知道C++编译中间是有几个步骤的,预处理、编译和链接,每个cpp或c文件都会被编译成目标文件,然后这些目标文件在通过链接生成可执行文件。那么考虑一下这种情况,假如现在我有两个cpp文件分别是x.cpp和y.cpp,上面的模板虚函数,我在x.cpp文件中实例化了

void foo(int& t);
void foo(float& t);

而在y.cpp中实例化了

void foo(int& t);
void foo(bool& t);

那么x.o和y.o中的A类的虚表都含有两天记录,但是函数符号却并不一样,那么为了实现模板虚函数,进行链接的时候就需要对虚表合并去重了,先抛去实现代价的问题,从理论上看起来的确是可行的。

然而事情并不是到此为止了,我们知道目标文件不只是可以链接成可执行文件,还可以链接成静态库和动态库,对于静态库,再进行链接的时候和普通链接差别不大,但是动态库就没有那么好运了。

考虑这样的一种情况,在动态库里面定义了上面的模板函数,而且实例化了

void foo(int& t);
void foo(float& t);

这两个虚函数,B类型的虚表在动态链接库已经确定,两条记录,但是在我们的程序里恰好调用了上面的模板虚函数,这时候实例化了

void foo(bool& t);

那么此时为了继续下去,就得修改动态链接库中B类的虚表了,为它添加一条记录,很显然是行不通的。至于为什么行不通,抛开程序段的可读写的问题不谈,如果真的可修改,那么这个类型的每个实例都可能会守到其它实例的影响了,与类的设计原则相悖了。

至此为什么模板虚函数为什么行不通已经很明显了。

本人才疏学浅,凭自己的理解发表一点理解,有什么不正确之处还各路大神敬请批评指正。

标签:虚表,函数,实例,void,vs,foo,模板
来源: https://www.cnblogs.com/dragonxyl/p/15323785.html