百科问答小站 logo
百科问答小站 font logo



C++ 中的基类为何在析构函数中添加 virtual? 第1页

  

user avatar   yao-dong-27 网友的相关建议: 
      

基类的 virtual 析构函数是为了解决一个问题:当用基类类型的指针去delete一个派生类的实例的时候,可以让派生类的析构函数被调用。

       class B {     virtual ~B(){} }  class A : public B {    ~A(){} }  B *p = new A(); delete p;      

如果在 class B 的 析构函数不是虚函数,那么当 delete p 时候 ~A() 是不会被调用的,如果需要在A对象析构时做些必要操作,那么就必须把 ~B()定义成虚函数。

那么这时候 ~B()还有没有机会执行呢,有的,析构函数的机制就是子类的析构会自动调用父类的析构函数,~B()实际是被 ~A()调用的,这就形成一个完美的析构链条。


user avatar   s.invalid 网友的相关建议: 
      

嗯……你早上是怎么穿衣服的?


是这个顺序:

1、穿上内裤;

2、穿上裤子。

还是这个顺序:

1、穿上裤子;

2、穿上内裤。


除了超人,都应该是顺序1吧?


那,如果你是按顺序1穿裤子的,晚上你怎么脱衣服?

1、先脱内裤;

2、再脱长裤。

能这样脱吗?是不是得和穿的时候反序?


C++的类构造/析构是类似过程。你可以认为基类-派生类的构造/析构动作被压进了一个(逻辑上存在但看不到的)栈里;构造时先穿内裤……哦不,先构造基类对象,后穿……构造派生类对象;脱……析构的时候正好是相反顺序。

不按这个顺序会如何?

你晚上不脱裤子先脱内裤试试不就知道了。


注意这个“逻辑上存在但看不到”的栈:事实上,这个栈也是看得到的。

构造函数里,你可以给一个初始化列表,把类中成员对象全部初始化(按对象声明顺序,和初始化列表顺序无关;其中基类对象总是最先初始化的);而在析构函数里,虽然你看不见,但类中所有成员对象也会被自动析构(当然,你自己new出来的必须自己delete,谁申请谁释放、如何申请就如何释放,这是基本原则)。

这个对象,就包括了基类对象


换句话说,派生类析构过程是:

1、调用用户自己写的析构函数,完成相应操作;

2、按照类中成员对象的初始化顺序的逆序,逐个析构成员对象;

3、基类对象是第一个初始化的、隐含的“成员对象”,因此最后被析构;

4、析构基类对象自然就触发了它的析构函数。


你看,虽然没有刻意实现,但这就是一个栈逻辑——所以说数据结构一定得好好学,不然人家玩的千变万化,随随便便一个数据结构就嵌逻辑里面了,你哪找得到

注意仅仅是栈逻辑,实际上却是一个逻辑上的树结构;这个树结构的根节点是基类,子节点是派生类。并且,这个树是单向的,所有派生类都可以回溯到基类、但无法从基类找派生类——说起来很复杂,听起来好多黑体字要背,背完更糊涂了;但只要稍微动动脑筋、关注功能、自己完成设计(而不是被动学习),这事就是随便谁,随手一写都差不多。

程序领域,这种正向的“自己完成设计”比逆向的“分析学习别人现成实现、琢磨出对方思路”简单很多倍的案例比比皆是。因此我才一再强调,一定要自己独立实现一些东西,不要只看现成的项目。


至于为何析构函数一定要声明为virtual,否则就等于禁止继承……

很简单,virtual成员函数会通过一定方式保留一份跟踪记录(目前来说绝大多数编译器是用虚函数表实现的),这样才能不受指向该对象的指针类型的迷惑、正确找到对应函数。


换句话说,我们可以通过正确的对象类型找到正确的析构函数,对编译器来说这是基本操作。

但是,当使用基类指针指向派生类对象时,此时通过指针取到的对象类型显然是错误的;那么我们就必须通过虚函数表之类机制才能找到“继承栈”的“栈顶”——然后才能调用到正确的派生类析构函数。

换句话说,virtual这个声明,就等于为析构函数栈搞了个栈顶指针。


而一旦找到了正确的析构函数(栈顶指针),把整个栈清空就很简单了,并不需要额外的东西。

具体来说,在正确析构函数里编译器自动添加的类内部对象析构相关代码是硬编码的,是绝对正确的;换句话说,它必须知道(除了用户指定的析构代码外)类内部成员对象的正确析构顺序和析构方法,否则就无法正确完成其功能——而基类对象的析构不过是这个析构逻辑的一部分而已,甚至都不用为它开特例

再换句话说,“析构函数栈”本身是物理存在的,我们仅仅是不知道它的栈顶而已;一旦栈顶确定,剩下自然顺理成章。




  

相关话题

  最上层的语言和最底层的语言都无需设计模式? 
  指向指向指向指针的指针的指针的指针有什么用? 
  如何评价Qt6弃用qmake,全面转向CMake? 
  怎么使C++用最复杂的方法打hello world? 
  memcpy比循环赋值快吗?为什么? 
  指针数组初始化为 nullptr 和直接使用 memcpy 有什么区别? 
  为什么大学讲授 C 语言比讲授 C++ 的更多? 
  C++ 运动完上一个函数到下一个函数时,上一个函数里的变量值(结构体变量)为什么没保留住,变成零了? 
  为什么编译器过度优化导致线程安全问题? 
  malloc申请的内存能是虚拟内存吗,也就是申请的一块新的空间,刚申请就缺页吗? 

前一个讨论
什么是中华文明重新立于世界之巅的内在逻辑?
下一个讨论
为什么好多编程“牛人”不喜欢用 Microsoft Visual Studio?





© 2024-06-18 - tinynew.org. All Rights Reserved.
© 2024-06-18 - tinynew.org. 保留所有权利