C++ virtual 析构函数

copy自:http://zxjgoodboy.blog.sohu.com/61482463.html

在此基础上稍作修改

 

C++中虚析构函数的作用

我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明: 

有下面的两个类:

[cpp] view plaincopy
  1. #include <iostream>  
  2. using namespace std;  
  3. class ClxBase  
  4. {  
  5. public:  
  6.     ClxBase() {};  
  7.     virtual ~ClxBase() {cout<<"AAA"<<endl;};  
  8.     virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };  
  9. };  
  10. class ClxDerived : public ClxBase  
  11. {  
  12. public:  
  13.     ClxDerived() {};  
  14.     ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };  
  15.     void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };  
  16. };  
  17. int main()  
  18. {  
  19.     ClxBase *pTest = new ClxDerived;  
  20.     pTest->DoSomething();  
  21.     delete pTest;  
  22. }  

输出结果:

Do something in class ClxDerived!

Output from the destructor of class ClxDerived!

AAA

 这个很简单,非常好理解。

但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:

Do something in class ClxDerived!

AAA

也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
    所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
    当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

 

你需要virtual析构函数吗

使用VC的class wizard自动生成一个类,会得到两个空的函数:构造函数和virtual析构函数。为什么析构函数要声明成virtual呢?

如果一个类要被使用成多态(polymorphic)的,那么这个virtual是必须的。比如:

[cpp] view plaincopy
  1. #include <iostream>  
  2. class Animal  
  3. {  
  4.   char* ap;  
  5. public:  
  6.    
  7.   Animal()  
  8.   {  
  9.     ap = new char;  
  10.     std::cout << "Animal ctor" << std::endl;  
  11.   }  
  12.   virtual void foo()  
  13.   {  
  14.     std::cout << "Animal::foo" << std::endl;  
  15.   }  
  16.   virtual ~Animal()  
  17.   {  
  18.     std::cout << "Animal dtor" << std::endl;  
  19.     delete ap;  
  20.   }  
  21. };  
  22. class Dog : public Animal  
  23. {  
  24.   char* dp;  
  25. public:  
  26.   Dog()  
  27.   {  
  28.     dp = new char;  
  29.     std::cout << "Dog ctor" << std::endl;  
  30.   }  
  31.   virtual void foo()  
  32.   {  
  33.     std::cout << "Dog::foo" << std::endl;  
  34.   }  
  35.   virtual ~Dog()  
  36.   {  
  37.     delete dp;  
  38.     std::cout << "Dog dtor" << std::endl;  
  39.   }  
  40. };  
  41. int main()  
  42. {  
  43.   Animal* pa = new Dog();  
  44.   pa->foo();  
  45.   delete pa;  
  46.   return 0;  
  47. }  
 

 

delete pa 实际上相当于:
 pa->~Animal();
 释放pa所指向的内存(或许是free(pa))。
在 这里,因为~Animal()是virtual的,尽管是通过Animal类型的指针调用的,根据v-table的信息,~Dog()被正确调用到。如果 把virtual属性去掉,那么被调用的是~Animal(),Dog类的构造函数被调用而析构函数未被调用,构造函数中分配的资源没有释放,从而产生了 内存泄漏。析构函数缺省声明为virtual,就可以避免这一问题。

可另一个问题是,有时virtual是不需要的。如果一个类不会被继承,比如一个utility类,该类完全是静态方法;或者一些类尽管可能会被继承,但不会被使用成多态的,即除了析构函数外,没有其他的方法是virtual的,这时就可以把virtual属性去掉。

去掉析构函数的virtual属性后,因为该类中没有其他的virtual函数,所以编译时不会生成v-table,这样就节省了编译时间,并减少了最终生成的程序的大小。更重要的是,遵从这一规则,给该类的维护者一个信息,即该类不应被当作多态类使用。

同样,当作一个抽象时,如果你模仿Java的interface,声明了如下的虚基类:

[cpp] view plaincopy
  1. class AbstractBase  
  2. {  
  3.  virtual method1() = 0;  
  4.  virtual method2() = 0;  
  5. };  
 

 

那么应该给它增加一个空的virtual析构函数:
 virtual ~AbstractBase(){}

如果你对COM比较熟悉,可能会注意到,COM interface中并没有这个virutal构造函数。这是因为,COM通过使用引用计数的机制来维护对象。当你使用完一个COM对象,调用Release()时,COM的内部实现检查引用技术是否为零。如果是,则调用
 delete this;
因为Release()是virtual的,所以该COM对象对应的正确的派生类被调用,delete this会调用正确的析构函数,达到了使用virtual析构函数的效果。

 

 

定义纯虚析构函数(pure virtual destructor)zz

纯虚成员函数通常没有定义;它们是在抽象类中声明,然后在派生类中实现。比如说下面的例子:

[cpp] view plaincopy
  1. class File //an abstract class  
  2. {  
  3. public:  
  4.  virtual int open(const string & path, int mode=0x666)=0;  
  5.  virtual int close()=0;  
  6. //...  
  7. };   
 

但是,在某些情况下,我们却需要定义一个纯虚成员函数,而不仅仅是声明它。最常见的例子是纯虚析构函数。在声明纯虚析构函数时,不要忘了同时还要定义它。

[cpp] view plaincopy
  1. class File //abstract class  
  2. {  
  3. public:  
  4.  virtual ~File()=0; //declaration of a pure virtual dtor  
  5. };  
  6. File::~File() {} //definition of dtor   
 

 

为什么说定义纯虚析构函数是非常重要的

 

派生类的析构函数会自动调用其基类的析构函数。这个过程是递归的,最终,抽象类的纯虚析构函数也会被调用。

如果纯虚析构函数只被声明而没有定义,那么就会造成运行时(runtime)崩溃。(在很多情况下,这个错误会出现在编译期,但谁也不担保一定会是这样。)纯虚析构函数的哑元实现(dummy implementation,即空实现)能够保证这样的代码的安全性。

[cpp] view plaincopy
  1. class DiskFile : public File  
  2. {  
  3. public:  
  4.  int open(const string & pathname, int mode);  
  5.  int close();  
  6.  ~DiskFile();  
  7. };  
  8. File * pf = new DiskFile;  
  9. //. . .  
  10. delete pf; //OK, ultimately invokes File::~File()   

在某些情况下定义其它纯虚成员函数可能也是非常有用的(比如说在调试应用程序以及记录应用程序的日志时)。例如,在一个不应该被调用,但是由于一个缺陷而被调用的基类中,如果有一个纯虚成员函数,那么我们可以为它提供一个定义。

[cpp] view plaincopy
  1. class Abstract  
  2. {  
  3. public:  
  4.  virtual int func()=0;  
  5. //..  
  6. };  
  7. int Abstract::func()  
  8. {  
  9. std::cerr<<"got called from thread " << thread_id<<  
  10.              "at: "<<gettimeofday()<<std::endl;  
  11. }   

这样,我们就可以记录所有对纯虚函数的调用,并且还可以定位错误代码;不为纯虚函数提供定义将会导致整个程序无条件地终止。


虚构造函数(virtual constructor)

C++不支持直接的虚构造函数。虚 拟机制的设计目的是使程序员在不完全了解细节(比如只知该类实现了某个界面,而不知该类确切是什么东东)的情况下也能使用对象。但是,要建立一个对象,可 不能只知道“这大体上是什么”就完事——你必须完全了解全部细节,清楚地知道你要建立的对象是究竟什么。所以,构造函数当然不能是虚的了。但是,可通过虚函数 virtual clone()(对于拷贝构造函数)或虚函数 virtual create()(对于默认构造函数),得到虚构造函数产生的效果。

注意:子类成员函数clone()的返回值类型故意与父类成员函数clone()的不同。这种特征被称为“协变的返回类型”(Covariant Return Types),该特征最初并不是C++语言的一部分,VC6.0以下版本编译器不支持这样的写法。

虚析构函数(virtual destructor)

当你可能通过基类指针删除派生类对象时,建议使用虚析构函数。虚函数绑定到对象的类的代码,而不是指针/引用的类。如果基类有虚析构函数,delete basePtr(基类指针)时,*basePtr 的对象类型的析构函数被调用,而不是该指针的类型的析构函数。

简单讲,这个类有虚函数就应该有虚析构函数。一旦你在类中加上了一个虚函数,你就已经需要为每一个对象支付空间代价(每个对象一个指针),所以这时使析构函数成为虚拟的通常不会额外付出什么。

对于那些trivial且没有子类的类,虚析构函数只会增加开销,不要使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(八)企业部分之nginx+tomcat+memcached负载均衡集群搭建

【server1】vim /usr/local/lnmp/tomcat/conf/context.xml<Context>......<Manager className"de.javakaffee.web.msm.MemcachedBackupSessionManager"memcachedNodes"n1:172.25.45.1:11211,n2:172.25.45.2:11211"failoverNodes"n1"req…

泛型算法(二十三)之排列算法

1、is_permutation(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2)&#xff1a;C11版本&#xff0c;判断两个序列是否为同一元素集的两个排列。 std::vector<int> c1 {1, 2, 3, };std::vector<int> c2 {1, 2, 3, 1, 3};//判断两个序…

C++ 虚函数经典深入解析

from&#xff1a;https://blog.csdn.net/gggg_ggg/article/details/45915505C中的虚函数的作用主要是实现了多态的机制。 关于多态&#xff0c;简而言之就是用父类型别的指针指向其子类的实例&#xff0c;然后通过父类的指针调用实际子类的成员函数。 这种技术可以让父类的指针…

21OGNL与ValueStack(VS)-静态方法访问

转自&#xff1a;https://wenku.baidu.com/view/84fa86ae360cba1aa911da02.html 在LoginAction中增加如下方法&#xff1a;public static String getSta() { return "这是LoginAction中的静态方法"; } 然后在loginSuc.jsp中增加如下代码&#xff1a; 调用Action中的静…

win7通过easyBCD引导ubuntu

我电脑配置了固态和传统双硬盘&#xff0c;SSD已经装了win7&#xff0c;然后在传统硬盘上安装ubuntu&#xff0c;结果安装完成后看不到ubuntu的入口。因为跟win7不是装在一个驱动设备上&#xff0c;所以使用easyBCD的Linux&#xff0f;BCD选项也无法正确引导。最后通过easyBCD的…

深入理解C++中的explicit关键字

深入理解C中的explicit关键字kezunhaigmail.com http://blog.csdn.net/kezunhaiC中的explicit关键字只能用于修饰只有一个参数的构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的&#xff0c; 跟它相对应的另一个关键字是implicit, 意思是隐藏的,构造函数默认情况下即声…

JAVA面试中问及HIBERNATE与 MYBATIS的对比,在这里做一下总结(转)

hibernate以及mybatis都有过学习&#xff0c;在java面试中也被提及问道过&#xff0c;在项目实践中也应用过&#xff0c;现在对hibernate和mybatis做一下对比&#xff0c;便于大家更好的理解和学习&#xff0c;使自己在做项目中更加得心应手。 第一方面&#xff1a;开发速度的对…

Caffe源码解析4: Data_layer

转载请注明出处&#xff0c;楼燚(y)航的blog&#xff0c;http://home.cnblogs.com/louyihang-loves-baiyan/ data_layer应该是网络的最底层&#xff0c;主要是将数据送给blob进入到net中&#xff0c;在data_layer中存在多个跟data_layer相关的类 BaseDataLayerBasePrefetchingD…

理解C++中拷贝构造函数

拷贝构造函数的功能是用一个已有的对象来初始化一个被创建的同样对象&#xff0c;是一种特殊的构造函数&#xff0c;具有一般构造函数的所有特性&#xff0c;当创建一个新对象的时候系统会自动调用它&#xff1b;其形参是本类对象的引用&#xff0c;它的特殊功能是将参数代表的…

IDEA mybatis-generator-maven-plugin 插件的使用

2019独角兽企业重金招聘Python工程师标准>>> pom.xml中添加插件 <plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuratio…

python优秀网友学习笔记推荐

AstralWindMr.Seven 转载于:https://www.cnblogs.com/migongci0412/p/5154892.html

深入理解CRITICAL_SECTION

摘要临界区是一种防止多个线程同时执行一个特定代码节的机制&#xff0c;这一主题并没有引起太多关注&#xff0c;因而人们未能对其深刻理解。在需要跟踪代码中的多线程处理的性能时&#xff0c;对 Windows 中临界区的深刻理解非常有用。本文深入研究临界区的原理&#xff0c;以…

webpack进阶之插件篇

上一篇博客讲解了webpack环境的基本&#xff0c;这一篇讲解一些更深入的内容和开发技巧。基本环境搭建就不展开讲了 一、插件篇 1. 自动补全css3前缀 autoprefixer 官方是这样说的&#xff1a;Parse CSS and add vendor prefixes to CSS rules using values from the Can I Use…

QT:QObject 简单介绍

QObject 是所有Qt对象的基类。QObject 是Qt模块的核心。它的最主要特征是关于对象间无缝通信的机制&#xff1a;信号与槽。 使用connect()建立信号到槽的连接&#xff0c;使用disconnect()销毁连接&#xff0c;使用blockSignals()暂时阻塞信号以避免无限通知循环&#xff0c;使…

利用malloc定义数组

使用malloc方法时&#xff0c;应导入文件 #include<malloc.h> 1.利用malloc定义一维数组 int *num (int *)malloc(sizeof(int)*8); // 定义一个一维数组有8个元素&#xff0c;等价于 int num[8]; 2.利用malloc定义二维数组 int **num &#xff08; int **&#xff09…

C++中基类的析构函数为什么要用virtual虚析构函数

from&#xff1a;https://blog.csdn.net/iicy266/article/details/11906457知识背景要弄明白这个问题&#xff0c;首先要了解下C中的动态绑定。 关于动态绑定的讲解&#xff0c;请参阅&#xff1a; C中的动态类型与动态绑定、虚函数、多态实现 正题直接的讲&#xff0c;C中基类…

第二章 Python基本元素:数字、字符串和变量

Python有哪些内置的数据类型&#xff1a; True False #布尔型 42 100000000 #整型 3.14159 1.0e8 #浮点型 abcdes #字符串 2.1 变量、名字和对象 python中统一的形式是什么&#xff1f; 对象&#xff0c;所有的对象都是以对象的形式存在…

Mac - 设置NSButton 的背景色

- (void)drawRect:(NSRect)dirtyRect {[super drawRect:dirtyRect];[[NSColor clearColor] setFill];NSRectFill(self.bounds);self.wantsLayer YES;self.layer.cornerRadius 8;self.layer.masksToBounds YES; } 转载于:https://www.cnblogs.com/741162830qq/p/5157046.html…

C++中static关键字作用总结

from&#xff1a;https://www.cnblogs.com/songdanzju/p/7422380.html1.先来介绍它的第一条也是最重要的一条&#xff1a;隐藏。&#xff08;static函数&#xff0c;static变量均可&#xff09; 当同时编译多个文件时&#xff0c;所有未加static前缀的全局变量和函数都具有全局…

C Primer Plus 第7章 C控制语句:分支和跳转 7.4 一个统计字数的程序

2019独角兽企业重金招聘Python工程师标准>>> 首先&#xff0c;这个程序应该逐个读取字符&#xff0c;并且应该有些方法判断何时停止&#xff1b;第二&#xff0c;它应该能够识别并统计下列单位&#xff1a;字符、行和单词。下面是伪代码描述&#xff1a; read a cha…