漫谈C++为何不支持模板虚函数

首先,咱们要区分模板类虚函数和模板虚函数,话很少说,先上代码:ide

模板类虚函数函数

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++的多态是经过虚表实现的,对于含有虚函数的类,会为其定义一个虚表,每一个实例化的对象都有一个指向该虚表的指针,因此一样的类,含有虚函数的类的实例大小比不含虚函数的多上一个指针的大小,虚表里为每一个虚函数维护着一条跳转记录,这些跳转地址在编译期就被肯定了,存放在类定义模块的数据段中,在程序运行期是不可修改的。那么这跟模板虚函数有什么关系呢?code

让咱们了解一点关于模板的特性,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中实例化了class

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类的虚表了,为它添加一条记录,很显然是行不通的。至于为何行不通,抛开程序段的可读写的问题不谈,若是真的可修改,那么这个类型的每一个实例均可能会守到其它实例的影响了,与类的设计原则相悖了。

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

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

相关文章
相关标签/搜索