析构函数是类的一个成员函数,用于释放类的对象在生存期程序为其分配的内存资源。由于析构函数是由程序自动调用的,那么我们就需要正确的分析出析构函数何时被调用。以下是我在学习过程中遇到的几种易产生误区的调用场景:

场景1> 对于一个全局函数,如果函数的形参的数据类型是类的对象,那么在函数作用域结束时,会自动调用析构函数,将形参的内存资源析构掉。同时,全局函数内部定义了一个临时对象tmp,在执行return语句时,程序将调用拷贝构造函数,把临时对象tmp拷贝给一个副本,称副本为匿名对象。故最终return出来的对象是匿名对象而非临时对象tmp。而tmp会被析构。因此,这段代码实际调用了两次析构函数。代码如下:

 1 #include "iostream"
 2 using namespace std;
 3 
 4 class Test
 5 {
 6 public:
 7     int a;
 8 public:
 9     Test(int a = 0)
10     {
11         this->a = a;
12     }
13     Test(const Test &obj)
14     {
15         a = obj.a;
16     }
17     ~Test()
18     {
19         cout<<"我是析构函数"<<endl;
20     }
21 };
22 
23 Test MyCopy(Test p)
24 {
25     Test tmp = p;
26     return tmp; //先析构形参p,再析构tmp
27 }
28 
29 void main()
30 {
31     Test t1(2);
32     Test t2 = MyCopy(t1);
33     
34     cout<<"hello..."<<endl;
35     system("pause");
36     return ;
37 }

输出结果为:

场景2> 另外,主函数中的32行,返回的匿名对象通过“=”号赋值给t2,此时由于采用的是初始化的方式,因此编译器会直接将匿名对象的名称更改为t2,即直接转化为t2,不会调用析构函数。而下面一种情况则需要调用,请看代码:

Test t2;
t2 = MyCopy(t1);

由于此时代码采用的是赋值的方式,因此编译器会调用拷贝构造函数将匿名对象拷贝给t2,同时匿名对象被析构。这样,析构函数就被调用了三次,程序更改后输出结果如下:

场景3> 当全局函数形参为引用时,不会调用析构函数。代码如下:

Test MyCopy(Test &p)
{
    Test tmp = p;
    return tmp;
}

void main()
{
    Test t1(2);
    Test t2 = MyCopy(t1);
    
    cout<<"hello..."<<endl;
    system("pause");
    return ;
}

由于引用的作用相当于指针,因此传递的只是对象所在的内存空间地址。又函数返回时采用初始化的方式,因此析构函数仅被执行一次。输出结果如下: