`
weihe6666
  • 浏览: 441689 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

虚析构函数

    博客分类:
  • C++
C++ 
阅读更多
虚析构函数


一 、为何要单独讨论虚析构函数?虚函数在类中到底有什么作用?

看如下代码:
#include <iostream>
using namespace std;

class enemytarget {
public:
	enemytarget() 
	{
		cout << "Call the Base Constructor!\n"; 
		++numtargets; 
       cout << "numtargets :" << numtargets << endl;
	}
	enemytarget(const enemytarget&) 
	{
		cout << "Call the Base Copy Constructor!\n"; 
		++numtargets;
        cout << "numtargets :" << numtargets << endl;
	}
	//virtual ~enemytarget() 
	 ~enemytarget() 
	{
        cout << "Call the Base  Destructor!\n"; 
		--numtargets; 
		cout << "numtargets :" << numtargets << endl;
	}

	static size_t numberoftargets()
	{ return numtargets; }

	virtual bool destroy();       // 摧毁enemytarget对象后
	// 返回成功

private:
	static size_t numtargets;     // 对象计数器
};

bool enemytarget::destroy()
{
   if(numtargets == 0)
	   return true;
}

// 类的静态成员要在类外定义;
// 缺省初始化为0
size_t enemytarget::numtargets;


class enemytank: public enemytarget {
public:
	enemytank() 
	{
		cout << "Call the Inherit Constructor!\n"; 
		++numtanks; 
		cout << "numbertanks :" << numtanks << endl; 
	}

	enemytank(const enemytank& rhs)
		: enemytarget(rhs)
	{ 
		cout << "Call the Inherit Copy Constructor!\n"; 
		++numtanks; 
		cout << "numbertanks :" << numtanks << endl; 
	}

	~enemytank() 
	{
		cout << "Call the Inherit  Destructor!\n"; 
		--numtanks; 
		cout << "numbertanks :" << numtanks << endl; 
	}

	static size_t numberoftanks()
	{ return numtanks; }

	virtual bool destroy();

private:
	static size_t numtanks;         // 坦克对象计数器
};

bool enemytank::destroy()
{
 if(numtanks == 0)
	 return true;
}

size_t enemytank::numtanks;

int main()
{
	enemytarget *targetptr = new enemytank[10];
	//enemytank *targetptr = new enemytank[10];
    delete [] targetptr;
	return 0;
}


输出结果为:
Call the Base Constructor!
numtargets :1
Call the Inherit Constructor!
numbertanks :1
Call the Base Constructor!
numtargets :2
Call the Inherit Constructor!
numbertanks :2
Call the Base Constructor!
numtargets :3
Call the Inherit Constructor!
numbertanks :3
Call the Base Constructor!
numtargets :4
Call the Inherit Constructor!
numbertanks :4
Call the Base Constructor!
numtargets :5
Call the Inherit Constructor!
numbertanks :5
Call the Base Constructor!
numtargets :6
Call the Inherit Constructor!
numbertanks :6
Call the Base Constructor!
numtargets :7
Call the Inherit Constructor!
numbertanks :7
Call the Base Constructor!
numtargets :8
Call the Inherit Constructor!
numbertanks :8
Call the Base Constructor!
numtargets :9
Call the Inherit Constructor!
numbertanks :9
Call the Base Constructor!
numtargets :10
Call the Inherit Constructor!
numbertanks :10
Call the Base  Destructor!
numtargets :9
Call the Base  Destructor!
numtargets :8
Call the Base  Destructor!
numtargets :7
Call the Base  Destructor!
numtargets :6
Call the Base  Destructor!
numtargets :5
Call the Base  Destructor!
numtargets :4
Call the Base  Destructor!
numtargets :3
Call the Base  Destructor!
numtargets :2
Call the Base  Destructor!
numtargets :1
Call the Base  Destructor!
numtargets :0

可以看出子类的析构函数没有被调用,为何没有被调用?

enemytarget *targetptr = new enemytank[10];targetptr 是基类的对象,当我们用基类的对象去delete派生类对象时会出现此类问题。当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。为了避免这个问题,只需要使enemytarget的析构函数为virtual。

如果把基类修改如下:
	virtual ~enemytarget() 
	{
        cout << "Call the Base  Destructor!\n"; 
		--numtargets; 
		cout << "numtargets :" << numtargets << endl;
	}


输出结果为:
Call the Base Constructor!
numtargets :1
Call the Inherit Constructor!
numbertanks :1
Call the Base Constructor!
numtargets :2
Call the Inherit Constructor!
numbertanks :2
Call the Base Constructor!
numtargets :3
Call the Inherit Constructor!
numbertanks :3
Call the Base Constructor!
numtargets :4
Call the Inherit Constructor!
numbertanks :4
Call the Base Constructor!
numtargets :5
Call the Inherit Constructor!
numbertanks :5
Call the Base Constructor!
numtargets :6
Call the Inherit Constructor!
numbertanks :6
Call the Base Constructor!
numtargets :7
Call the Inherit Constructor!
numbertanks :7
Call the Base Constructor!
numtargets :8
Call the Inherit Constructor!
numbertanks :8
Call the Base Constructor!
numtargets :9
Call the Inherit Constructor!
numbertanks :9
Call the Base Constructor!
numtargets :10
Call the Inherit Constructor!
numbertanks :10
Call the Inherit  Destructor!
numbertanks :9
Call the Base  Destructor!
numtargets :9
Call the Inherit  Destructor!
numbertanks :8
Call the Base  Destructor!
numtargets :8
Call the Inherit  Destructor!
numbertanks :7
Call the Base  Destructor!
numtargets :7
Call the Inherit  Destructor!
numbertanks :6
Call the Base  Destructor!
numtargets :6
Call the Inherit  Destructor!
numbertanks :5
Call the Base  Destructor!
numtargets :5
Call the Inherit  Destructor!
numbertanks :4
Call the Base  Destructor!
numtargets :4
Call the Inherit  Destructor!
numbertanks :3
Call the Base  Destructor!
numtargets :3
Call the Inherit  Destructor!
numbertanks :2
Call the Base  Destructor!
numtargets :2
Call the Inherit  Destructor!
numbertanks :1
Call the Base  Destructor!
numtargets :1
Call the Inherit  Destructor!
numbertanks :0
Call the Base  Destructor!
numtargets :0

这种情况就避免了派生类析构函数不被调用的错误。


二、什么情况下声明析构函数为虚函数,什么情况下不用声明为虚函数?

如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。

看下面的例子:
// 一个表示2d点的类
class point {
public:
  point(short int xcoord, short int ycoord);
  ~point();

private:
  short int x, y;
};

如果一个short int占16位,一个point对象将刚好适合放进一个32位的寄存器中。另外,一个point对象可以作为一个32位的数据传给用c或fortran等其他语言写的函数中。但如果point的析构函数为虚,情况就会改变。

实现虚函数需要对象附带一些额外信息,以使对象在运行时可以确定该调用哪个虚函数。对大多数编译器来说,这个额外信息的具体形式是一个称为vptr(虚函数表指针)的指针。vptr指向的是一个称为vtbl(虚函数表)的函数指针数组。每个有虚函数的类都附带有一个vtbl。当对一个对象的某个虚函数进行请求调用时,实际被调用的函数是根据指向vtbl的vptr在vtbl里找到相应的函数指针来确定的。

虚函数实现的细节不重要(当然,如果你感兴趣,可以阅读条款m24),重要的是,如果point类包含一个虚函数,它的对象的体积将不知不觉地翻番,从2个16位的short变成了2个16位的short加上一个32位的vptr!point对象再也不能放到一个32位寄存器中去了。而且,c++中的point对象看起来再也不具有和其他语言如c中声明的那样相同的结构了,因为这些语言里没有vptr。所以,用其他语言写的函数来传递point也不再可能了,除非专门去为它们设计vptr,而这本身是实现的细节,会导致代码无法移植。

所以基本的一条是,无故的声明虚析构函数和永远不去声明一样是错误的。实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。

三、当类里没有虚函数的时候,也会带来非虚析构函数问题。

例子如下:
template<class t>                // 基类模板
class array {                    // (来自条款13)
public:
  array(int lowbound, int highbound);
  ~array();

private:
  vector<t> data;
  size_t size;
  int lbound, hbound;
};

template<class t>
class namedarray: public array<t> {
public:
  namedarray(int lowbound, int highbound, const string& name);
  ...

private:
  string arrayname;
};


如果在应用程序的某个地方你将指向namedarray类型的指针转换成了array类型的指针,然后用delete来删除array指针,那你就会立即掉进“不确定行为”的陷阱中。
int main()
{
namedarray<int> *pna =
  new namedarray<int>(10, 20, "impending doom");
array<int> *pa;
pa = pna;                // namedarray<int>* -> array<int>*
delete pa;               // 不确定! 实际中,pa->arrayname
                         // 会造成泄漏,因为*pa的namedarray
                         // 永远不会被删除
}

现实中,这种情形出现得比你想象的要频繁。让一个现有的类做些什么事,然后从它派生一个类做和它相同的事,再加上一些特殊的功能,这在现实中不是不常见。namedarray没有重定义array的任何行为——它继承了array的所有功能而没有进行任何修改——它只是增加了一些额外的功能。但非虚析构函数的问题依然存在.

所以一个类被定义为基类要注意虚析构函数的声明。



分享到:
评论

相关推荐

    虚析构函数示例

    在C++编程中,虚析构函数是一个至关重要的概念,特别是在处理对象的继承和多态性时。本文将深入探讨虚析构函数的作用、工作原理以及何时需要使用它。 首先,我们来理解什么是析构函数。析构函数是C++中的一个特殊...

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

    C++中虚析构函数的作用 C++中虚析构函数的作用是为了确保当使用基类的指针删除派生类的对象时,派生类的析构函数被正确调用。这种机制可以防止内存泄漏和其他潜在的问题。 在C++中,析构函数是用来释放对象占用的...

    虚基类 虚函数成员 虚析构函数

    在C++编程语言中,虚基类、虚函数成员和虚析构函数是面向对象编程中的关键概念,它们对于理解和实现多态性至关重要。多态性允许我们编写更灵活、可扩展的代码,使得程序能处理多种不同类型的对象。下面将详细解释这...

    为何要虚析构函数.docx

    对于虚析构函数来说,虚函数表的存在使得可以根据实际对象的类型来决定调用哪个析构函数,从而实现了在对象销毁时的逆序操作:先调用派生类的析构函数,然后调用基类的析构函数,以保证派生类中新增的资源也能被正确...

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

    C++ 中基类的析构函数为什么要用 virtual 虚析构函数 C++ 中基类的析构函数为什么要用 virtual 虚析构函数?这是一个非常重要的问题。在 C++ 中,基类的析构函数如果不使用 virtual 虚析构函数,可能会导致内存泄漏...

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

    C++中虚析构函数的作用及其原理分析 C++中的虚析构函数是一种特殊的析构函数,用于避免内存泄露和防止内存泄露。当子类中有指针成员变量时,虚析构函数可以确保在删除基类指针时调用子类的析构函数,以释放子类中的...

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

    } // 非虚析构函数 private: char* ptr_a_; }; class B : public A { public: B() { ptr_b_ = new char[20]; } ~B() { delete[] ptr_b_; } // 派生类的析构函数 private: char* ptr_b_; }; void foo() { A* ...

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

    在C++编程语言中,虚函数(Virtual Functions)和虚析构函数(Virtual Destructors)是面向对象编程中的重要概念,特别是在处理继承和多态性时显得尤为关键。本资料详细介绍了这两个主题,并通过实例进行了深入讲解...

    构造函数不能声明为虚函数,析构函数可以声明为虚函数

    2. **动态绑定的正确性**:在类的继承体系中,通过虚析构函数可以确保动态绑定正确地执行。当通过基类指针删除派生类对象时,动态绑定机制将识别到实际类型并调用相应的析构函数。 3. **代码可扩展性和安全性**:...

    C++虚析构函数、纯虚析构函数

     虚析构函数  析构函数的工作方式是:底层的派生类(most derived class)的析构函数先被调用,然后调用每一个基类的析构函数。  因为在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非...

    C++中析构函数定义成虚函数的原因.doc

    当通过基类指针或引用调用虚析构函数时,会调用对应的派生类析构函数,实现了多态性。 3. **提高代码健壮性和可维护性**:对于大型项目来说,良好的设计习惯至关重要。将基类的析构函数声明为虚函数可以减少未来...

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

    在C++编程语言中,虚析构函数(Virtual Destructor)是一个非常关键的概念,它主要用于处理多态性(Polymorphism)和动态类型(Dynamic Type)。本文将深入探讨虚析构函数的作用,以及为何在基类中声明虚析构函数是...

    深析C++析构函数

    3. **声明虚析构函数:** 如果一个类有可能被继承,则应该声明虚析构函数,以确保正确的析构顺序。 4. **遵循RAII原则:** 使用资源获取即初始化(Resource Acquisition Is Initialization, RAII)的设计模式,利用...

    C_虚构造函数和虚析构函数.pdf

    C_虚构造函数和虚析构函数.pdf

    简要解读C++的动态和静态关联以及虚析构函数

    虚析构函数是C++中一个重要的概念,特别是在涉及对象生命周期管理和资源释放时。当一个对象通过指针或引用被删除时,如果该对象是派生类,那么首先会调用派生类的析构函数,然后调用基类的析构函数。这是因为基类的...

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

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

    C++析构函数 快速学习

    C++中,有两种类型的析构函数:非虚析构函数和虚析构函数。对于基类,如果基类的析构函数声明为虚的(`virtual`),那么通过基类指针或引用删除派生类对象时,将调用正确的派生类析构函数,这是多态性的体现。不声明...

    C++箴言:避免析构函数调用虚函数

    ### C++箴言:避免析构函数调用虚函数 #### 概述 在C++编程中,理解和遵循良好的设计模式对于确保程序的稳定性和可维护性至关重要。其中一个经常被提及的原则是“避免在析构函数中调用虚函数”。这一原则在C++语言...

    析构函数应用

    6. **可以是虚函数**:如果希望在派生类中覆盖基类的析构函数,则可以在基类中声明析构函数为虚函数。 #### 三、示例代码解析 下面通过给出的代码示例来详细了解析构函数的应用: ```cpp #include using ...

Global site tag (gtag.js) - Google Analytics