虚析构函数、虚函数结合考题变种

简介: 虚析构函数、虚函数结合考题变种详解

虚析构函数、虚函数结合考题变种

1、[Effective C++原则07]:为多态基类声明virtual 析构函数。

class CSimpleClass
{
public:
CSimpleClass(){ cout << "CSimpleClass" <<endl; }
~CSimpleClass() { cout <<"~CSimpleClass" << endl; }
private:
};
 
class CDerived : public CSimpleClass
{
public:
CDerived() { cout << "CDerived" << endl; }
~CDerived() { cout << "~CDerived" << endl; }
private:
};
 
int main()
{
CSimpleClass *pSimple = new CDerived;
delete pSimple;
 
return 0;
}

执行结果如下:

image.png

显然,CDerived 对象没有被析构!

1、 造成上述不同的原因何在?

“C++标准”明确指出,当派生类对象经由一个基类指针pBaseObject被删除,而该基类带有一个non-virtual析构函数,其结果未有定义(即不可预知)。实际执行时,如上面第一个图示,会产生bug,派生类的对象没有被销毁。

这就形成诡异的“局部销毁”对象,形成资源泄露。

 

2、 什么时候需要基类析构函数声明为虚函数?什么时候不需要基类的析构函数为虚函数?

该问题涉及析构函数何时应该为虚函数。注意:对于上面的基类BaseClass,

若析构函数不为虚函数,sizeof(BaseClass) = 1。

若析构函数为虚函数,sizeof(BaseClass) = 4。

至于为什么包含构造函数、非虚析构函数的类的大小为1个字节。解释如下:

空类类的大小比如BaseClass没有构造、析构函数,本来sizeof(BaseClass)应该为0,但是我们声明该类型实例的时候,必须在内存中占用一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定,visual studio中每个空类型的实例占用1个字节的空间。

 而加上构造函数、析构函数或其他非虚类型的函数以后呢?由于这些非虚类型的函数的地址只与类有关,而与类的实例无关,编译器不会因为非虚函数的增加而添加任何额外的信息。

那么为什么析构函数变成虚函数后,大小就变成4个字节了呢?主要原因是:C++一旦发现类中有虚函数,就会为该类生成虚函数表,并在该类型的每一个实例中添加指向虚函数表的指针。在32位机器上,一个指针占4个字节的空间,所以求sizeof大小为4。而在64位机器上,一个指针占用8个字节的空间,因此sizeof大小为8。

即为类析构函数声明为虚析构函数是以付出内存为代价的。所以,无端将所有类的析构函数声明为虚函数,就向从未声明它们是虚函数一样,都是错误的。

 

总结如下:

(1)带多态性质的基类应用声明一个虚析构函数。如果类中带有任何虚函数,它就应该拥有一个虚析构函数;

(2)设计类的目的如果不作为基类,或者不是为了具备多态性,就不应该声明虚析构函数。

——参考《Effective C++》条款7;《剑指Offer》

 

2、[Effective 原则09]:绝不在构造和析构过程中调用virtual函数。

【原因】:base class的执行更早于derived class的构造函数,当base class的构造函数执行的时候derived class的成员变量尚未初始化。

【如果不】:执行的结果不会动态联编,依然执行其所在层的虚函数。

【示例如下】:

class CSimpleClass
{
public:
       CSimpleClass() { cout << "CSimpleClass"<< endl; foo();} //调用了本层的foo
       virtual ~CSimpleClass() { cout <<"~CSimpleClass" << endl; foo();} //调用了本层的foo
       virtual void foo() { cout << "CSimpleClass::foo()" << endl; }
private:
};
 
classCDerived : public CSimpleClass
{
public:
       CDerived() { cout <<"CDerived" << endl; foo(); }
       ~CDerived() { cout <<"~CDerived" << endl; foo(); }
       void foo() { cout<< "CDerived::foo()" << endl; }
private:
};
 
int main()
{
       CSimpleClass *pSimple = new CDerived;
       delete pSimple;
 
       return 0;
}

执行结果如下:

image.png

3.综合1,2的笔试题如下:

class CBase
{
public:
       CBase(){ cout << "CBase ctor" << endl; foo(); }    //调用本层的foo
        ~CBase() { cout<< "CBase dtor" << endl; foo(); }  //未加virtual,且调用本层的foo
private:
       virtualvoid foo(){ cout << "Base::foo()" << endl; } //
};
 
class CDerived : public  CBase
{
public:
       CDerived(){cout << "CDerived ctor" << endl; foo(); }
       ~CDerived(){cout << "CDerived dtor" << endl; foo(); }
 
private:
       virtual void foo(){ cout << "Derived::foo()" << endl; }
};
 
int main()
{
       CBase*pBase = new CDerived;
       delete pBase;
 
       return 0;
}

结合原则1,2.正确的输出结果是:

image.png

显然,CDerived的析构函数不会被调用,因为CBase的析构函数非虚函数。

在CBase的构造和析构函数中调用虚函数,仅会执行本层的定义,不会下调。还是遵照[Effective 原则09]:绝不在构造和析构过程中调用virtual函数为上策。


作者:铭毅天下
来源:CSDN
原文:https://blog.csdn.net/laoyang360/article/details/8068858
版权声明:本文为博主原创文章,转载请附上博文链接!

相关文章
|
3月前
|
存储 C++ 容器
第十四章:C++虚函数、继承和多态详解
第十四章:C++虚函数、继承和多态详解
29 0
|
1月前
|
存储 安全 编译器
【C/C++ 多态核心 20240115更新】C++虚函数表:让多态成为可能的关键
【C/C++ 多态核心 20240115更新】C++虚函数表:让多态成为可能的关键
32 0
|
3月前
|
C++
第十二章:C++中的this指针详解
第十二章:C++中的this指针详解
20 0
|
3月前
|
存储 C++
第十一章 C++成员函数与静态成员详解
第十一章 C++成员函数与静态成员详解
23 0
|
6月前
|
存储 算法 编译器
虚函数和多态+虚析构函数 知识点总结 C++程序设计与算法笔记总结(五) 北京大学 郭炜
虚函数和多态+虚析构函数 知识点总结 C++程序设计与算法笔记总结(五) 北京大学 郭炜
31 0
|
12月前
|
存储 安全 算法
带你走入虚函数和多态的世界(c++)
📖作者介绍:22级树莓人(计算机专业),热爱编程<目前在c++阶段>——目标Windows,MySQL,Qt,数据结构与算法,Linux,多线程,会持续分享学习成果和小项目的 📖作者主页:热爱编程的小K 📖专栏链接:c++ 🎉欢迎各位→点赞👏 + 收藏💞 + 留言🔔​ 💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🐾 ———————————————— 版权声明:本文为CSDN博主「热爱编程的小K」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_72157449
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
423 0
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
264 1
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
|
C++ Perl
虚函数和纯虚函数的区别,再不学就被卷没了
虚函数和纯虚函数的区别,再不学就被卷没了
91 0