c++中究竟什么时候调用析构函数?

When exactly is the destructor called in C++

本文关键字:析构函数 调用 什么时候 究竟 c++      更新时间:2023-10-16

考虑以下代码

int i;
class A
{
public:
   ~A()
   {
       i=10;
   }
};
int foo()
{
   i=3;
   A ob;
   return i;
}
int main()
{
   cout << foo() << endl;
   return 0;
}

因为i是全局的,所以我认为这个程序的输出应该是10。当ob超出作用域时,调用析构函数,将i的值设置为10

局部变量超出作用域,并执行其析构函数,函数返回后。这意味着当~A()运行时,返回值已经是3。相反,如果你这样做……

int foo()
{
   {
      i=3;
      A ob;
   }
   return i;
}

ob将超出作用域并在返回语句之前死亡,结果将是10。

额外作用域通常以这种方式用于raii活动类型,如scoped_lock,以便仅对函数的一部分进入某些特定的副作用状态。

int foo()
{
   i=3;
   A ob;
   return i;
} // <- ~A() is called here
ob超出作用域时,在函数foo()的末尾调用

~A()。这是在计算返回值(即i的副本,当时的3的值)之后。

为了在函数退出之前将i设置为10,您需要强制ob"提前退出作用域"。最简单的方法是在A ob周围添加一个额外的作用域。

int foo()
{
   i=3;
   {
     A ob;
   } // <- ~A() is called here and sets i to 10
   return i;
}

ob在返回执行后超出作用域。考虑一下,如果ob参与了返回表达式的求值,并且它在返回之前超出了作用域,将会发生什么。

ob直到作用域结束,即在return语句之后的}处才会超出作用域。这时析构函数被调用,但此时i已经被求值了,所以它的旧值被返回。

ob超出作用域时调用~A()。大会证明:

<__Z3foov>:
    push   %ebp
    mov    %esp,%ebp
    push   %ebx
    sub    $0x10,%esp
    movl   $0x3,0x407020        /* <-- setting i to 3 */
    mov    0x407020,%ebx        /* <-- loading i to %ebx */
    lea    -0x5(%ebp),%eax
    mov    %eax,%ecx
    call   403860 <__ZN1AD1Ev>  /* <-- calling destructor */
    mov    %ebx,%eax            /* <-- returnign already calculated i from %ebx */
    add    $0x10,%esp
    pop    %ebx
    pop    %ebp
    ret  
<__ZN1AD1Ev>:               /* <-- the destructor */
    push   %ebp
    mov    %esp,%ebp
    sub    $0x4,%esp
    mov    %ecx,-0x4(%ebp)
    movl   $0xa,0x407020    /* <-- set i to 10 */
    leave  
    ret  

可以看到,在将返回值放入%ebx之后调用析构函数,当析构函数完成后,返回值再次移动到%eax。对于您将来关于c++代码行为的问题,反汇编是您最好的朋友。