主动调用析构函数的陷阱/误区
问题背景
在笔者之前写的一个项目里面,有一个统计耗时的类,其构造函数用于记录代码块的开始时间,析构函数用于记录代码块的结束时间,从而计算出代码块的耗时。
由于 调用析构函数 不等于 对象生命周期结束,而对象生命周期结束的时候会再次自动调用析构函函数,导致结束时间被记录了两次,从而导致耗时计算错误。
析构函数和对象生命周期的关系
析构函数是用于释放对象占用的资源,当对象生命周期结束时,会自动调用析构函数。对象生命周期结束的时机有两种:
- 对象在栈上创建,当对象所在的作用域结束时,对象生命周期结束。
- 对象在堆上创建,当调用
delete
释放对象时,对象生命周期结束。
所以无论是哪种情况,生命周期的结束都和析构函数无关,只是在生命周期结束的时候会调用析构函数。
ps: 类对象的析构函数只能释放那些动态分配的资源,对于类对象管理的那些栈上的资源是生命周期结束时自动释放的。
主动调用析构函数
- 此时析构函数只是一个普通的成员函数。
- 如果在析构函数中释放动态分配的资源,那么当生命周期结束时,析构函数会被再次调用,导致释放两次资源。
代码验证
class MyClass {
public:
MyClass() {
count = 0; // 基本类型成员变量
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
public:
int count; // 基本类型成员变量
};
int main()
{
MyClass obj;
obj.~MyClass();
obj.count = 10;
std::cout << obj.count << std::endl;
std::cout << "End of main" << std::endl;
return 0;
}
输出结果:
Destructor called
10
End of main
Destructor called
结论:
- 主动调用析构函数不会结束对象的生命周期。
- 主动调用析构函数之后成员变量依然可以被访问。
- 对象生命周期结束时,析构函数会被再次调用。
总结: 不要主动调用析构函数
如何提早结束对象的生命周期
- 最简单的就是改变作用域,但是很不灵活。
- 使用new创建对象,然后使用delete释放对象。(不推荐,因为容易忘记释放)
- 使用智能指针,如
std::shared_ptr
、std::unique_ptr
等。然后使用reset
方法释放对象。
例如:
std::shared_ptr<MyClass> obj_ptr = std::make_shared<MyClass>();
obj_ptr.reset();