C++ virtual 析构函数

C++中虚析构函数的做用html

咱们知道,用C++开发的时候,用来作基类的类的析构函数通常都是虚函数。但是,为何要这样作呢?下面用一个小例子来讲明: ios

有下面的两个类:程序员

 

[cpp] view plain copy安全

 

  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. }  

 

输出结果:app

 

Do something in class ClxDerived!函数

Output from the destructor of class ClxDerived!this

AAAspa

 

 

这个很简单,很是好理解。
可是,若是把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:.net

 

Do something in class ClxDerived!设计

AAA

 

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

 

 

 

你须要virtual析构函数吗

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

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

 

[cpp] view plain copy

 

  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 plain copy

 

  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 plain copy

 

  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 plain copy

 

  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 plain copy

 

  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 plain copy

 

  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且没有子类的类,虚析构函数只会增长开销,不要使用。

相关文章
相关标签/搜索