析构函数和对象生命周期的关系

 

主动调用析构函数的陷阱/误区

问题背景

在笔者之前写的一个项目里面,有一个统计耗时的类,其构造函数用于记录代码块的开始时间,析构函数用于记录代码块的结束时间,从而计算出代码块的耗时。

由于 调用析构函数 不等于 对象生命周期结束,而对象生命周期结束的时候会再次自动调用析构函函数,导致结束时间被记录了两次,从而导致耗时计算错误。

析构函数和对象生命周期的关系

析构函数是用于释放对象占用的资源,当对象生命周期结束时,会自动调用析构函数。对象生命周期结束的时机有两种:

  1. 对象在栈上创建,当对象所在的作用域结束时,对象生命周期结束。
  2. 对象在堆上创建,当调用delete释放对象时,对象生命周期结束。

所以无论是哪种情况,生命周期的结束都和析构函数无关,只是在生命周期结束的时候会调用析构函数。

ps: 类对象的析构函数只能释放那些动态分配的资源,对于类对象管理的那些栈上的资源是生命周期结束时自动释放的。

主动调用析构函数

  1. 此时析构函数只是一个普通的成员函数。
  2. 如果在析构函数中释放动态分配的资源,那么当生命周期结束时,析构函数会被再次调用,导致释放两次资源。

代码验证

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

结论:

  1. 主动调用析构函数不会结束对象的生命周期。
  2. 主动调用析构函数之后成员变量依然可以被访问。
  3. 对象生命周期结束时,析构函数会被再次调用。

总结: 不要主动调用析构函数

如何提早结束对象的生命周期

  1. 最简单的就是改变作用域,但是很不灵活。
  2. 使用new创建对象,然后使用delete释放对象。(不推荐,因为容易忘记释放)
  3. 使用智能指针,如std::shared_ptrstd::unique_ptr等。然后使用reset方法释放对象。

例如:

std::shared_ptr<MyClass> obj_ptr = std::make_shared<MyClass>();
obj_ptr.reset();

参考资料

  1. 关于c++显示调用析构函数的陷阱
  2. C++中为什么手动调用析构函数之后对象还能使用