C++ 虚函数及多态原理详解

news/2024/7/3 12:27:21 标签: 虚函数, 多态

概述

虚函数机制作为C++面向对象的重要支撑,但是我们对它知之甚少,因为它由编译器实现,程序员很少用到。今天就对虚函数进行整理,理解了虚函数有一些东西就很好理解了,这样出去吹牛会更有底气。

1、虚函数的支持原理

每一个拥有虚函数的类会产生出一堆指向virtual functions的指针,放在表格中,这个表格被称为virtual table(虚函数表)

定义类的对象时编译器会给这个对象插入一个合成指针,指向相关的virtual table。这个指针通常被称为vptr(虚指针)。vptr的设定和重置都由每个类的构造、析构和赋值运算符自动完成。

需要注意的是:每个类所关联的type_info(类型信息)也经由virtual table被指出,通常放在第一个表格。如下:

class Point
{
public:
	Point( float xval);
	virtual ~Point();
	float x() const;
	
protected:
	virtual ostream& print( ostream& os) const;
	
	float _x;
};

在这里插入图片描述
这样做优点在于它的空间和存取时间的效率。缺点是增加或删除虚函数需要进行重新编译。

2、单一继承下的虚函数

虚函数的继承主要用于实现多态多态表示一个public base clas 的指针或引用,寻址出一个派生类的对象。表现为,调用同一个函数,展现出不同的动作。

多态的底层实现需要知道两个细节:1、指针或引用所指向的真实类型,这可使我们可以选择正确的函数。2、正确的函数实际的位置,以便调用。

通过上一节的讲解,你可能已经知道了,类型信息存放在virtual table的第一个表格。

一个单一继承类中只会有一个virtual table,每一个table内含所有虚函数的的地址,其中虚函数包括:

  1. 当前类所定义的虚函数,它会改写(overriding)一个可能存在的基类虚函数
  2. 继承自基类的虚函数。这是在派生类决定不改写虚函数时才会出现
  3. 虚函数也会包含

每个虚函数都会被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的虚函数关联。比如:索引 0 为类类型信息,索引 1 为虚析构,索引 2 为纯虚函数…等。如下举例:

class Point
{
public:
	virtual ~Point();
	virtual Point& mult( float ) = 0;
	float x() const { return _x; };
	virtual float y() const { return 0; };
	virtual float z() const { return 0; };
	//...
	
protect:
	Point( float x = 0.0 );
	float _x;
};

class Point2d : public Point
{
public:
	Point2d( float x = 0.0, float y = 0.0 ) : Point(x),_y(y){}
	~Point2d();
	
	//改写基类虚函数
	Point2d& mult( float );
	float y() const { return _y; };
	//....
protected:
	float _y;
};

class Point3d : public Point2d
{
public:
	Point3d( float x = 0.0, float y = 0.0, float z = 0.0 ) : Point2d(x,y),_z(z){}
	~Point3d();
	
	//改写基类虚函数
	Point3d& mult( float );
	float z() const { return _z;	};
	//......
protected:
	float _z;
};

类的继承关系如上,各个类的virtual table布局则如下所示:
在这里插入图片描述
通过上面也可以看出:
1、派生类继承基类的虚函数,会把基类的虚函数地址拷贝到相应的表格中。如:基类的z()总是放在索引为4的表格中。
2、派生类自己的虚函数实例,必须放到对应的表格中。如:类型都要放到索引为0的表格,虚析构要放到索引为1的表格。
3、新加入一个虚函数,virtual table的尺寸会增大一个表格,用来存放新的虚函数

以上的举例有一个特殊的情况:对于表达式(不管ptr指向的是什么类型):

ptr->z();

因为每个virtual table表中都含有虚函数z,而每个对象中都会拥有vptr,而vptr又指向唯一的virtual table,编译器知道调用函数对应函数的索引值,所以编译器会转化为:

(*ptr->vptr[ 4 ]) (ptr);

这种就不需要在执行期确定类型了。

3、多重继承下的虚函数

多重继承中支持虚函数,主要是第二个及后续的基类身上。简单来说就是会有一个偏移值(offset)。下面来看个例子:

class Base1
{
public:
	Base1();
	virtual ~Base1();
	virtual void speakClearly();
	virtual Base1* clone() const;
protected:
	float	data_Base1;
};

class Base2
{
public:
	Base2();
	virtual ~Base2();
	virtual void mumble();
	virtual Base2* clone() const;
protected:
	float	data_Base2;
};

class Derived : public Base1 , public Base2
{
public:
	Derived();
	virtual ~Derived();
	virtual Derived* clone() const;
protected:
	float	data_Derived;
};

每个类中virtual table 的布局如下:
在这里插入图片描述
在多重继承之下,一个派生类内含n-1个额外的virtual tables,n表示其基类的个数(单一继承不会有额外的virtual table)。

虽然有多个虚函数表,但是当你创建对象时,会给对象中的vptr赋值不一样。比如:

Base1* ptr1 = new Derived();		//vptr指向上面的虚函数表
Base2* ptr2 = new Derived();		//vptr指向下面的虚函数

为了提高执行期链接器的效率,有的编译器会对多个虚函数表进行连接,然后通过表格的首地址加上一个offset获得实际的虚函数表。

4、虚拟继承下的虚函数

对于虚继承我们之前整理过类成员分布虚继承,一般而言会在虚函数表中有指向虚基类的指针,然后根据这个指针在操作。

还有的编译器是在对象中拥有多个vptr,分别指向不同的虚函数表,这样使用时就不会出错。

因为虚继承比较复杂,而且是间接寻址,效率比较低,所以尽量少用。

5、VS IDE中查看类内存分布

在Visual Studio中,右击项目,在属性(Properties)-> C/C++ -> 命令行(Command Line)-> 附加选项(Additional Options)中输入/d1 reportAllClassLayout即可在输出窗口中查看类的内存分布。

感谢大家,我是假装很努力的YoungYangD(小羊)。

参考资料:
《深度探索 C++对象模型》


http://www.niftyadmin.cn/n/1796949.html

相关文章

山东省谷歌地球高程DEM等高线下载

一、概述 山东,因居太行山以东而得名,简称“鲁”,省会济南。先秦时期隶属齐国、鲁国,故而别名齐鲁。山东地处华东沿海、黄河下游、京杭大运河中北段,是华东地区的最北端省份。西部为黄淮海平原,连接中原&am…

【爆牙齿】微软的坟墓:Windows 7。(二)

互联网应用时代 我认为,从Vista开始,微软不断失败的根本原因是其作为本地软件操作系统的设计思想落后于时代,而优化后的7有同样的问题。所以无论他们多优秀,面对时代,就一句:顺我者昌,逆我者亡。…

新疆自治区谷歌地球高程DEM等高线下载

一、概述 新疆维吾尔自治区,简称新,位于中国西北边陲,首府乌鲁木齐,是中国五个少数民族自治区之一,也是中国陆地面积最大的省级行政区,面积166万平方公里,占中国国土总面积六分之一。 新疆地处亚…

GNS3 ProxyArp(查看路由器是否具有转发功能)

R2是否可以把R1的数据转发出去,参看http://www.cnblogs.com/qq76211822/p/5129134.html 命令:show ip interface f0/0 转载于:https://www.cnblogs.com/qq76211822/p/5129157.html

数据结构(第一篇:基础知识)

概述 我们程序员都听过一句话:程序 数据结构 算法。可见数据结构的重要性。下面就对数据结构中一些概念的总结。 数据:所以能保存到计算机中的符号。包括文本、声音、图像等。 数据元素:数据的基本单位,也称节点或记录。 数据…

云南省谷歌地球高程DEM等高线下载

一、概述 云南,简称云(滇),省会昆明,位于中国西南的边陲,北回归线横贯云南省南部,属低纬度内陆地区,东部与贵州省、广西壮族自治区为邻,北部与四川省相连,西北…

每天必备:实实在在的脸部保湿秘诀! - 健康程序员,至尚生活!

脸部保湿秘诀:1、喝足够水,每天至少要喝8大杯水,当然喝更多就与日俱增好,除了喝水外,水果可别忘记多吃哦!2、尽量避免风吹日晒。3、不要用过热的水洗脸,洗脸后一定要使用化妆水来补充水份。4、选…

浙江省谷歌地球高程DEM等高线下载

一、概述 浙江,简称“浙”,省会杭州。境内最大的河流钱塘江,因江流曲折,称之江、折江,又称浙江,省以江名。 地处中国东南沿海长江三角洲南翼,东临东海,南接福建,西与安徽…