建议:
A. 不要对局部对象显式地调用析构函数,在创建该局部对象的代码块的 } 处,析构函数会自动被调用。这是语言所保证的;自动发生。没有办法阻止它。而两次调用同一个对象的析构函数,调用两次意味着第二次会试图清理已经被清理过了的,根本不再存在的数据!这是件会导致运行时错误的问题,并且在编译的时候不会告诉你!
但是也会有一些时候,我们想要将一个局部对象在被创建的代码块 } 之前被析构的话,举个例子吧。比如假设析构 File 对象的作用是关闭文件。现在假定你有一个 File 类的对象 f,并且你想 File f 在 f 对象的作用范围结束(也就是 } )之前被关闭:
Void function
{
File f
// ... [这些代码在 f 打开的时候执行] ...
// <— 希望在此处关闭 f
// ... [这些代码在 f 关闭后执行] ...
}
1. 人为的添加一个{…}.将局部对象的生命周期长度包裹于这个人为的{…}中就可以了。这样在我们人为的 } 的后面析构函数将会被自动调用。绝大多数的时候我们是可以人为的将局部对象包裹于这个人为的{…}中的,来限制其生命周期。
2. 不能人为的用{…}去包裹一些代码,这个时候我们可以通过增加一个类似于析构函数的成员函数来实现类似的功能。但是我们千万不要调用析构函数本身。
例如,File类的情况下,可以添加一个close()方法。典型的析构函数只是调用close()方法。注意close()方法需要标记File对象,以便后续的调用不会再次关闭一个已经关闭的文件。举例来说,可以将一个fileHandle_数据成员设置为 -1,并在开头检查fileHandle_是否已经等于-1:
class File {
public:
void close();
~File();
// ...
private:
int fileHandle_; // 当且仅当文件打开时fileHandle_ >= 0
};
File::~File()
{
close();
}
void File::close()
{
if (fileHandle_ >= 0) {
// ... [执行一些操作-系统调用来关闭文件] ...
fileHandle_ = -1;
}
注意其他的 File方法可能也需要检查fileHandle_是否为 -1(检查文件是否被关闭了)。还要注意任何没有实际打开文件的构造函数,都应该将fileHandle_设置为 -1。
B. 在编写析构函数的时候,并不需要显式地调用成员对象的析构函数(排除place new情况)。因为类的析构函数(不论你是否显式地定义了)自动调用成员对象的析构函数。它们以出现在类声明中的顺序的反序被析构。
class Member {
public:
~Member();
// ...
};
class Fred {
public:
~Fred();
// ...
private:
Member x_;
Member y_;
Member z_;
};
Fred::~Fred()
{
// 编译器自动调用 z_.~Member()
// 编译器自动调用 y_.~Member()
// 编译器自动调用 x_.~Member()
}
我们在书写派生类的析构函数的时候,也不需要显示的调用基类的析构函数。派生类的析构函数(不论你是否显式地定义了)自动调用基类子对象的析构函数。基类在成员对象之后被析构。在多重继承的情况下,直接基类以出现在继承列表中的顺序的反序被析构。
class Member {
public:
~Member();
// ...
};
class Base {
public:
virtual ~Base(); // 虚析构函数
// ...
};
class Derived : public Base {
public:
~Derived();
// ...
private:
Member x_;
};
Derived::~Derived()
{
// 编译器自动调用 x_.~Member()
// 编译器自动调用 Base::~Base()
}
注意:虚拟继承的顺序相关性是多变的。如果你在一个虚拟继承层次中依赖于其顺序相关性,那你应该去别的地方查一下资料
析构函数的析构顺序简单的概括为一句话:“先构造的后析构,也就是说构造的顺序和析构的顺序反着的”。
比如我们有下面的语句:
void Creat
{
Class_Name A[10];
//other code…
}
此处我们创建了一个对象数组,则他最后的析构顺序为A[9],A[8]…A[0]
C. 一般不要自作聪明的去调用析构函数。或者要是你不嫌麻烦的话,析构之前最好先看看堆上的数据是不是已经被释放过了。
class aaa
{
public:
aaa(){}
~aaa(){cout<<"deconstructor"<<endl; }
void disp(){cout<<"disp"<<endl;}
private:
char *p;
};
void main()
{
aaa a;
a.~aaa();
a.disp();
}
这样的话,显示两次deconstructor,第一次析构相当于调用一个普通的成员函数,执行函数内语句,显示;第二次析构是编译器隐式的调用,增加了释放栈内存的动作,这个类未申请堆内存,所以对象干净地摧毁了,显示+对象摧毁;
class aaa
{
public:
aaa(){p = new char[1024];}
~aaa(){cout<<"deconstructor"<<endl; delete []p; p=NULL;}
void disp(){cout<<"disp"<<endl;}
private:
char *p;
};
void main()
{
aaa a;
a.~aaa();
a.disp();
}
这样的话,第一次显式调用析构函数,相当于调用一个普通成员函数,执行函数语句,释放了堆内存,但是并未释放栈内存,对象还存在(但已残缺,存在不安全因素);第二次调用析构函数,再次释放堆内存(此时报异常),然后释放栈内存,对象销毁