C++多态之虚基类析构函数的作用

需要注意的是,本文的前提是实现多态


虚表是每个含有虚函数的类中维护的一张保存着各个虚函数地址的表

每个含有虚函数的类中都保存着一个指向虚表的指针,而虚表中保存了该类各个虚函数的地址。


而当子类对象过期时,需要被销毁,如果父类对象没有将析构函数声明为virtual,则在通过父类指针销毁子类对象时,只会调用父类析构函数,而子类对象比父类对象多出来的部分则不会被销毁,所以需要将父类析构函数声明为virtual。下面来个例子:

#include <iostream>
#include <cstdlib>
using namespace std;

class Father
{
public:
	Father(){};
	virtual ~Father(){};
};

class Son : public Father
{
public:
	Son() { cout << "Object created.\n"; }
	~Son() { cout << "Son destructor called.\n"; }
};

int main(void)
{
	Father *p = new Son;
	delete p;
	system("pause");
	return 0;
}
输出如下:


说明子类析构函数被调用了。


接着,我们把父类析构函数的virtual去掉,看会出现什么。

#include <iostream>
#include <cstdlib>
using namespace std;

class Father
{
public:
	Father(){};
	~Father(){};
};

class Son : public Father
{
public:
	Son() { cout << "Object created.\n"; }
	~Son() { cout << "Son destructor called.\n"; }
};

int main(void)
{
	Father *p = new Son;
	delete p;
	system("pause");
	return 0;
}
输出如下:


通过这个实验说明,如果父类析构函数没有声明为virtual,则销毁子类时,子类对象的析构函数不会被调用,这会产生很严重的后果。

但是,如果没有涉及到内存分配,virtual也不是必须的。


另外需要说的是,使用指针访问对象的public虚函数成员时,程序是根据指向对象的类型来寻找对应的函数,而不是根据指针的类型来寻找对应的函数,这条规则对所有虚函数都成立,所以当子类被销毁时,父类指针指向的是子类对象,所以程序会找到子类的虚函数表,然后调用子类的析构函数,接着再调用父类的析构函数来释放父类分配的内存,这样才能使程序运行得井井有条。

而如果父类析构函数没有声明为virtual的话,那么上面的程序就会出错,因为父类、子类的析构函数都不在各自的虚函数表中,因为指针是父类类型的,所以会执行普通的直接调用public成员函数的方法来销毁对象。下面再来看个例子就明白了:

#include <iostream>
#include <cstdlib>
using namespace std;

class Father
{
public:
	Father(){};
	~Father(){};
};

class Son : public Father
{
public:
	Son() { cout << "Object created.\n"; }
	~Son() { cout << "Son destructor called.\n"; }
};

int main(void)
{
	cout << "Son1 ";
	Father *p1 = new Son;
	delete p1;

	cout << "Son2 ";
	Son *p2 = new Son;
	delete p2;
	system("pause");
	return 0;
}
输出如下:



由此可见,要使用父类指针的形式实现多态,我们在大多数情况下必须将父类析构函数声明为虚函数。而少数情况下,比如在类中没有static变量、enum常量等且没有内存分配的情况下,可以不使用virtual,但是不管有没有,要实现多态,都建议对父类析构函数使用virtual,因为如今计算机的高速计算速度,完全可以忽略这方面带来的微小的效率影响。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是DS小龙哥编写整理的C++入门指南PDF文档,适合C++初学者,C语言转C++工程师当做入门工具书学习。PDF里有完整示例、知识讲解,平时开发都可以复制粘贴,非常便捷。 目前一共写了7章,后续会持续更新资源包,更新后重新下载即可。 这是目前书籍的目录: C++入门指南 1 一、 C++语言基本介绍与开发环境搭建 1 1.1 C++简介 1 1.2 面向对象编程 1 1.3 Windows系统下搭建C++学习环境 2 二、C++基础入门 16 2.1 C++类和对象 17 2.2 C++命名空间 18 2.3 std标准命名空间 20 2.4 C++新增的标准输入输出方法(cin和cout) 22 2.5 C++规定的变量定义位置 24 2.6 C++新增的布尔类型(bool) 24 2.7 C++ 新增的new和delete运算符 25 2.8 C++函数的默认参数(缺省参数) 26 2.9 C++函数重载详解 28 2.10 C++新增的引用语法 30 三、 C++面向对象:类和对象 34 3.1 类的定义和对象的创建 34 3.2 类的成员变量和成员函数 36 3.3 类成员的访问权限以及类的封装 38 3.4 C++类的构造函数与析构函数 39 3.5 对象数组 47 3.6 this指针 50 3.7 static静态成员变量 52 3.8 static静态成员函数 53 3.9 const成员变量和成员函数 55 3.10 const对象(常对象) 56 3.11 友元函数和友元类 58 3.11.3 友元类 61 3.12 C++字符串 62 四、C++面向对象:继承与派生 75 4.1 继承与派生概念介绍 75 4.2 继承的语法介绍 75 4.3 继承方式介绍(继承的权限) 76 4.4 继承时变量与函数名字遮蔽问题 79 4.5 基类和派生类的构造函数 82 4.6 基类和派生类的析构函数 83 4.7 多继承 85 4.8 虚继承虚基类 88 五、C++多态与抽象类 91 5.1 多态概念介绍 91 5.2 虚函数 92 5.3 纯虚函数和抽象类 95 六、C++运算符重载 97 6.1 运算符重载语法介绍 97 6.2 可重载运算符与不可重载运算符 98 6.3 一元运算符重载 99 6.4 二元运算符重载 102 6.5 关系运算符重载 104 6.6 输入/输出运算符重载(>>、<<) 105 6.7 函数调用运算符 () 重载 106 6.8 重载[ ](下标运算符) 107 七、C++模板和泛型程序设计 108 7.1 函数模板 108 7.2 类模板 110

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值