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

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

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

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


[如果不]: 如果不声明为析构函数,可能出现的结果如下:Derived对象的成分没有被销毁,形成资源泄露、在调试上会浪费很长时间。


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


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


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


image.pngimage.png

相关文章
|
负载均衡 安全 Linux
在Linux中,什么是负载均衡,并且如何在Linux中实现它。
在Linux中,什么是负载均衡,并且如何在Linux中实现它。
|
3天前
|
人工智能 运维 安全
|
1天前
|
人工智能 异构计算
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
|
8天前
|
人工智能 JavaScript 测试技术
Qwen3-Coder入门教程|10分钟搞定安装配置
Qwen3-Coder 挑战赛简介:无论你是编程小白还是办公达人,都能通过本教程快速上手 Qwen-Code CLI,利用 AI 轻松实现代码编写、文档处理等任务。内容涵盖 API 配置、CLI 安装及多种实用案例,助你提升效率,体验智能编码的乐趣。
764 109
|
2天前
|
机器学习/深度学习 传感器 算法
Edge Impulse:面向微型机器学习的MLOps平台——论文解读
Edge Impulse 是一个面向微型机器学习(TinyML)的云端MLOps平台,致力于解决嵌入式与边缘设备上机器学习开发的碎片化与异构性难题。它提供端到端工具链,涵盖数据采集、信号处理、模型训练、优化压缩及部署全流程,支持资源受限设备的高效AI实现。平台集成AutoML、量化压缩与跨硬件编译技术,显著提升开发效率与模型性能,广泛应用于物联网、可穿戴设备与边缘智能场景。
171 127
|
3天前
|
算法 Python
【轴承故障诊断】一种用于轴承故障诊断的稀疏贝叶斯学习(SBL),两种群稀疏学习算法来提取故障脉冲,第一种仅利用故障脉冲的群稀疏性,第二种则利用故障脉冲的额外周期性行为(Matlab代码实现)
【轴承故障诊断】一种用于轴承故障诊断的稀疏贝叶斯学习(SBL),两种群稀疏学习算法来提取故障脉冲,第一种仅利用故障脉冲的群稀疏性,第二种则利用故障脉冲的额外周期性行为(Matlab代码实现)
230 152
|
5天前
|
Java 数据库 数据安全/隐私保护
Spring 微服务和多租户:处理多个客户端
本文介绍了如何在 Spring Boot 微服务架构中实现多租户。多租户允许单个应用实例为多个客户提供独立服务,尤其适用于 SaaS 应用。文章探讨了多租户的类型、优势与挑战,并详细说明了如何通过 Spring Boot 的灵活配置实现租户隔离、动态租户管理及数据源路由,同时确保数据安全与系统可扩展性。结合微服务的优势,开发者可以构建高效、可维护的多租户系统。
210 127