首页 > 其他分享 > 2.为什么析构函数一般写成虚函数

2.为什么析构函数一般写成虚函数

时间:2023-08-03 09:04:08浏览次数:45  
标签:调用 函数 基类 析构 派生类 写成 include

2.为什么析构函数一般写成虚函数

在C++实现多态里,有一个关于 析构函数的重写问题:基类中的析构函数如果是虚函数,那么派生类的析构函数就重写了基类的析构函数。这里他们的函数名不相同,看起来违背了重写的规则,但实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。那么为什么要把基类中的析构函数写成虚函数呢?

当使用多态特性,让基类指针指向派生类对象时,如果析构函数不是虚函数,通过基类指针销毁派生类对象时,会调用静态绑定的析构函数,也就是基类的析构函数,从而只能销毁属于基类的元素,导致派生类析构不完全,程序就会出现资源泄露或未定义行为。

当派生类中不存在使用动态资源或其他自定义析构行为时,可以不写为虚析构函数,来提高程序效率。但为了程序的可扩展性和健壮性,在使用多态特性时,一般都建议将基类的析构函数定义为虚函数。

在 C++ 中,只需要在基类中定义虚析构函数,派生类会自动继承这个虚属性。也就是说,如果基类的析构函数被声明为虚函数,那么所有派生类的析构函数都将自动成为虚函数。

如果你在派生类中显式地声明析构函数,无论你是否将其声明为虚函数,它都将是虚函数。因此,在派生类中声明虚析构函数并非必要,但是为了代码的清晰性,很多开发者仍然会在派生类中显式地声明虚析构函数。

这样的话,当我们看到派生类的代码时,我们就能立刻知道其析构函数是虚函数,这使得代码更易于理解。这也是一种被称为 "programming by contract" 的编程习惯,意味着类的设计者通过接口明确地表明了类的使用方式。

总结来说,基类的析构函数声明为虚函数后,派生类的析构函数自动成为虚函数,不论是否显式声明为虚函数。但为了代码清晰,有时候我们仍然会在派生类中显式地声明虚析构函数。

举个例子:

  • 当基类析构函数不是虚函数时:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class A 
{
public:
	A() { cout << "A的构造" << endl; }
	~A() { cout << "A的析构" << endl; }
	void Work() 
	{
		cout << "A工作" << endl;
	}
};//基类

class B :public A
{
public:
	B() { cout << "B的构造" << endl; }
	~B() { cout << "B的析构" << endl; }
	void Work() { cout << "B工作" << endl; }
}; //派生类

int main()
{
	A* p = new B;  //派生类对象赋给基类指针
	p->Work();//此时调用的是基类的成员函数,因为基类的成员函数覆盖了派生类的同名成员函数
	delete p;

	system("pause");
	return EXIT_SUCCESS;
}

输出:

A的构造
B的构造
A工作
A的析构
请按任意键继续. . .

可以看到在delete p的时候只调用了基类A的析构函数,并没有调用派生类B的析构函数,导致内存释放并不完全,出现内存泄漏的问题。

  • 然后将基类析构函数写为虚函数时
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	A() { cout << "A的构造" << endl; }
	virtual ~A() { cout << "A的析构" << endl; }
	void Work()
	{
		cout << "A工作" << endl;
	}
};//基类

class B :public A 
{
public:
	B() { cout << "B的构造" << endl; }
	~B() { cout << "B的析构" << endl; } //在派生类中重写的成员函数可以不加virtual关键字
	void Work() { cout << "B工作" << endl; }
};//派生类

int main()
{
	A* p = new B;  //派生类对象赋给基类指针
	p->Work();//此时调用的是基类的成员函数,因为基类的成员函数覆盖了派生类的同名成员函数
	delete p;

	system("pause");
	return EXIT_SUCCESS;
}

输出:

A的构造
B的构造
A工作
B的析构
A的析构
请按任意键继续.

可以看到这次在delete p的时候调用了派生类的析构函数,因为在调用派生类的析构函数后会自动调用基类的析构函数,这样整个派生类的对象被完全释放。

另外上面两个过程中我们发现执行 "p->Work();" 时,也就是p在调用同名成员函数的时候,调用的始终是基类的成员函数,这是因为基类的成员函数覆盖了派生类的同名成员函数,如果想要调用派生类的成员函数,同样将Work()设置为虚函数即可。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class A 
{
public:
	A() { cout << "A的构造" << endl; }
	virtual ~A() { cout << "A的析构" << endl; }
	virtual void Work() 
	{
		cout << "A工作" << endl;
	}
};

class B :public A
{
public:
	B() { cout << "B的构造" << endl; }
	~B() { cout << "B的析构" << endl; }
	void Work() { cout << "B工作" << endl; }
};

int main()
{
	A* p = new B;  //派生类指针转化成基类指针
	p->Work();
	delete p;
	system("pause");
	return EXIT_SUCCESS;
}

输出:

A的构造
B的构造
B工作
B的析构
A的析构
请按任意键继续. . .

内存切割:

参考:

C++:基类析构函数为什么要定义为虚函数

标签:调用,函数,基类,析构,派生类,写成,include
From: https://www.cnblogs.com/codemagiciant/p/17602311.html

相关文章

  • 算法-10--python shuffle函数_python中shuffle()方法的功能详解
     pythonshuffle函数_python中shuffle()方法的功能详解: python的概率分布中,洗牌算法是通过shuffle()方法实现的,shuffle()方法将列表的所有元素打乱,随机排列。Python既可以使用random.shuffle对列表进行洗牌,也可以使用random.shuffle随机播放字符串列表,本文向大家介绍python中......
  • C++入门到放弃(07)——构造函数和析构函数
    ​1.构造函数和析构函数是什么1.1构造函数通常一个类,其内部包含有变量和函数,当我们想要使用类的时候,总是会不得不面临这样一个问题,需要对类进行初始化,否则内部这些变量就会是随机值,导致程序出现异常。为此,我们需要在使用类之前对它进行初始化,C++就提供了这样一类特殊的函数——......
  • 5.说一说你了解的关于lambda函数的全部知识
    5.说一说你了解的关于lambda函数的全部知识1.利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象;2.每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closuretype)。那么在运行时,这个lambda表达式就会返回一......
  • 9.手写实现智能指针类需要实现哪些函数?
    9.手写实现智能指针类需要实现哪些函数?1.智能指针是一个数据类型,一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。除了指针对象外,我们还需要一个引用计数的指针设定对象的值,并将......
  • 5.C++中类的数据成员和成员函数内存分布情况
    5.C++中类的数据成员和成员函数内存分布情况非静态成员的数据类型大小之和。编译器加入的额外成员变量(如指向虚函数表的指针)。为了边缘对齐优化加入的padding。空类(无非静态数据成员)的对象的size为1,当作为基类时,size为0。C++类是由结构体发展得来的,所以他们的成员变......
  • 120.strcpy函数和strncpy函数的区别?哪个函数更安全?
    120.strcpy函数和strncpy函数的区别?哪个函数更安全?1.函数原型char*strcpy(char*strDest,constchar*strSrc)char*strncpy(char*dest,constchar*src,size_tn)2.安全性strcpy函数:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出(bufferOverflow)的错误......
  • [8月摸鱼计划]无法将“ssh”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
    无法将“ssh”项识别为cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次gitee生成自己的公钥之后,运行ssh-T [email protected]检测是否成功却说没办法识别ssh遇到了类似的问题在powershell会这样。所以我直接到gitbash里面去敲......
  • fluent:壁面函数/边界层/y+
    速度边界层根据速度边界层理论:具有黏性的流体,经过壁面附近流速下降。所以在壁面处流体速度可以认为u=0,随着离壁面越来越远,流体速度也会增加。为什么要用壁面函数为了不划分更细的网格也可以捕捉到边界层速度,引入了壁面函数的说法,也就是y+。定义分母是运动粘度,其中y表示距离......
  • 字符串转化为整数的C库函数
    #include<stdio.h>#include<stdlib.h>intmain(void){charstr[10]="12345";charstr1[10]="hello";intval;val=atoi(str);printf("val=%d,str=%s\r\n",val,str);val=atoi(s......
  • 进程注入检测 —— RtlCaptureStackBackTrace 获取当前函数的调用栈函数
    https://stackoverflow.com/questions/590160/how-to-log-stack-frames-with-windows-x64 https://cpp.hotexamples.com/examples/-/-/RtlCaptureStackBackTrace/cpp-rtlcapturestackbacktrace-function-examples.html  例子参考  平日里用VS开发工具在调时在Debug下有一个选......