`

C++ virtual 析构函数

阅读更多

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

在此基础上稍作修改

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

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

有下面的两个类:

输出结果:

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是必须的。比如:

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,声明了如下的虚基类:

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

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

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

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

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

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

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

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

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

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


虚构造函数(virtual constructor)

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

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

虚析构函数(virtual destructor)

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

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

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

分享到:
评论

相关推荐

    C++析构函数使用virtual的原因

    C++析构函数使用virtual的原因

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

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

    c++虚析构函数及虚函数的详细例子.rar

    c++ virtual 虚析构函数及虚函数的详细例子.rar

    C++虚析构函数的使用分析

    在C++中,不能声明虚构造函数,但可以声明虚析构函数。...例如以下代码,其中SomeClass是含有非virtual析构函数的一个类:SomeClass *p= new SomeClass;. . . . . .delect p;为p调用delect时,会自动调用SomeClass

    C++虚基类 虚函数 虚析构函数

    //析构函数做成员函数 }; Base::~Base()//成员函数实现 { cout; } class Derived:public Base { public: Derived(); ~Derived(); private: int *p; }; Derived::Derived() { p=new int(0);//从堆上分配一个int型...

    C++中确定基类有虚析构函数

    本文给大家介绍了C++中确定基类有虚析构函数的方法。

    详解C++中虚析构函数的作用及其原理分析

    C++中的虚析构函数到底什么时候有用的,什么作用呢。 一.虚析构函数的作用 总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类...

    C++开发培训资料

    当通过父类指针delete一个子类对象时,构造的行为未定义 在父类的析构函数前加virual,使用之成为虚析构函数 父类的析构函数在子类的析构函数执行...如果一个类中有virtual函数,那么该类就应该提供一个virual析构函数。

    C++多态性实验报告

    ○2先不将析构函数声明为virtual,在main函数中另设一个指向Circle类对象的指针变量,使它指向变量,使它指向grad1。运行程序,分析结果。 ○3不作第②点的修改而将析构函数声明为virtual,运行程序,分析结果。 (2...

    虚函数被类的构造析构函数和成员函数调用虚函数的执行过程

    代码如下:#include class base{public:  base() { std::cout<<std::endl;... virtual ~base() { std::cout<<std::endl; std::cout<<“base distructor”<<std::endl; func

    Effective C++ 中文版

    条款07:为多态基类声明Virtual析构函数 条款08:别让异常逃离析构函数 条款09:绝不在构造和析构过程中调用Virtual函数 条款10:令Operator=返回一个referenceto this 条款11:在Operator=中处理“自我赋值” ...

    华为C++笔试题全部汇总

    3. 父类的析构函数是非虚的,但是子类的析构函数是虚的,delete子类指针(指向该子类对象)[特殊情况,参见题5],会调用父类的析构函数(正确)//任何情况下删除子类都会调用到父类的析构函数 4.对于下面的类CA,...

    面向对象与C++试题.doc

    7、有关析构函数的说法,不正确的是( )。 A.析构函数有且仅有一个 B.析构函数和构造函数一样可以有形参 C.析构函数的功能是在系统释放对象之前作一些内存清理工作 D.析构函数无任何函数类型 8、类定义的内容...

    C++接口定义及实现举例

    一、接口的定义  有时候,我们得提供一些接口给别人使用。接口的作用,是提供一个与其他...//注意,好要定义此虚析构函数,能够避免其实现不能正常调用析构函数的问题 //提供给外面使用的接口一般采用纯虚函数 vi

    新手学习C++入门资料

    标准C++中的字符串类取代了C标准C函数库头文件中的字符数组处理函数。 C++中用来做控制态输入输出的iostream类库替代了标准C中的stdio函数库。 C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和...

    Effective+C+++3rd+chm+中文版(代码加亮)

    destructor(析构函数)什么时候应该是 virtual(虚拟)的?当 operator new(运算符 new)找不到足够的内存时它应该怎么办?类似这些的令人费神的细节是至关重要的,因为错误的做法几乎总是导致无法预料的,很可能...

    深入探讨C++父类子类中虚函数的应用

    析构函数可以是虚函数。1.父类Father.h: 代码如下:#pragma onceclass Father{public: Father(void); virtual ~Father(void); virtual int getCount();public: int count;};Father.cpp 代码如下:#include ...

    C++ Primer第四版【中文高清扫描版】.pdf

    15.4.4 虚析构函数 495 15.4.5 构造函数和析构函数中的虚函数 497 15.5 继承情况下的类作用域 497 15.5.1 名字查找在编译时发生 498 15.5.2 名字冲突与继承 498 15.5.3 作用域与成员函数 499 15.5.4 虚函数与作用域 ...

    Effective C++之2.构造析构赋值运算

    编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。 条款06:如不想使用编译器自动生成的函数,就应该明确拒绝 为了驳回编译器暗自提供的机能,可将相应的成员函数...

    mocks_injector:C++ 自动模拟注入器

    在智能指针的情况下自动注册所需的析构函数(支持测试 unique_ptr) 使用作为 Mocking 库 使用作为依赖注入库 单元测试 # include # include # include struct ilogger { virtual ~ilogger () { }; virtual void...

Global site tag (gtag.js) - Google Analytics