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

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

C++ virtual 析构函数相关推荐

  1. 条款七 为多态基类声明virtual析构函数

    1.如果基类中的成员函数是virtual类型的,其继承类中相应的函数也是virtual类型,并且基类对象的引用指向继承类对象时, 基类就可以调用继承类函数,否则调用的是基类函数 class base ...

  2. Effective C++ .07 virtual析构函数的提供

    主要讲了, 1. virtual析构函数的作用与调用顺序 2. 使用时机,并不是使用了继承就要把基类的析构函数变为虚函数(virtual),只有当用于多态目的时才进行一个virtual析构函数的定义. ...

  3. virtual析构函数(作用)

    virtual析构函数的作用? . 大家知道,析构函数是为了在对象不被使用之后释放它的资源,虚函数是为了实现多态.那么把析构函数声明为vitual有什么作用呢?请看下面的代码: 1 #include ...

  4. 读书笔记_Effective_C++_条款七:为多态基类声明virtual析构函数

    严格来说,多态分为编译时多态和运行时多态,编译时多态是函数的重载,而运行时多态则是迟绑定技术,即根据基类指针或引用的实际指向来决定采取何种行动,一般来说,多态特指运行时多态.下面我们来举有关C++多态 ...

  5. effective C++读书笔记--【条款07:为多态基类声明virtual析构函数】

    问题 base class的指针p,指向一个derived class,如果: base class带着一个non-virtual析构函数: derived class对象经由这个base class ...

  6. virtual析构函数的作用?

    *************************************************** 更多精彩,欢迎进入:http://shop115376623.taobao.com ****** ...

  7. Effective C++条款7:为多态基类声明virtual析构函数

    要点: 带多态性质的基类或者class中有virtual函数,就应该将析构函数声明为virtual,例如 class TimeKeeper { public:TimeKeeper();~TimeKee ...

  8. 为何virtual析构函数不能是protected?

    如下例: class Base { public:     virtual foo() = 0; protected:     virtual ~Base(){}; }; class Derived ...

  9. effective c++:virtual函数在构造函数和析构函数中的注意事项

    effective c++:virtual函数在构造函数和析构函数中的注意事项 如不使用自动生成函数要明确拒绝 对于一个类,如果你没有声明,c++会自动生成一个构造函数,一个析构函数,一个copy构造 ...

最新文章

  1. 2022-2028年中国氟膜行业市场全景评估及发展策略分析报告
  2. detach detach_ pytorch
  3. Jenkins实现SVN+Maven+Java项目的持续集成
  4. Linux aptget 参数,ubuntu下apt-get 命令参数大全
  5. python清空字典保留变量方法_python学习day06--02字典增删差改以及字符串的一些方法...
  6. Leetcode 数据结构与算法题解大全——目录(推荐收藏,持续更新)
  7. 方法内联在JVM中有多积极?
  8. 模线性方程(中国剩余定理+扩展中国剩余定理)
  9. python的底层实现,Python封装底层实现原理详解(通俗易懂)
  10. Java即时类| toString()方法与示例
  11. PP视频如何将默认缓存清晰度设置成超清
  12. python xgboost实战_史上最详细的XGBoost实战
  13. Failed to connect to GitHub to update the CocoaPods/Specs specs repo 问题
  14. Centos-RedHat 添加路由
  15. DSP2812之定时器0
  16. Win7专业版密码忘了使用U深度启动U盘清除登录密码
  17. WDF基本对象和句柄定义
  18. ENSPLAB笔记:配置VXLAN(分布式网关,BGP EVPN方式)(Part1)
  19. ubuntu14.04 64位安装H3C iNode客户端
  20. 平面解析几何----余弦定理求焦点三角形的面积

热门文章

  1. axios 中文文档、使用说明
  2. SpringMVC请求处理流程、springMVC工作流程
  3. JavaScript几个难点
  4. Windows - Windows下安装MSI程序遇到2503和2502错误
  5. Xcode 修改工程名以及注意事项
  6. 多线程条件变量(pthread_cond_wait)用法
  7. 锐动SDK应用于行车记录仪
  8. Lua游戏开发----模块
  9. JSP+Javabean+Servlet实现用户注册
  10. 云数据库·ApsaraDB 产品6月刊