C++ 析构函数运行两次

Blu*_*all 3 c++ debugging gcc destructor clang

请参阅下面的程序。程序应该打印 1,因为 1 计数器对象仍然存在,但是当用 GCC 编译时,它打印 0。为什么呢?这是编译器错误吗?仅当从不同范围返回时才会发生这种情况。删除 if 可以在所有编译器上完全修复它。

#include <iostream>

int counter = 0;

class Counter {
public:
    Counter() {
        counter++;
    }
    ~Counter() {
        counter--;
    }
};

Counter test() {
    if (true) { // REMOVING THIS FIXES IT
        Counter c;
        return c;
    } else {
        throw std::logic_error("Impossible!");
    }
}

int main() {
    Counter c = test();
    std::cout << counter << std::endl; // 0 on GCC (incorrect), 1 on clang (correct)
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Sam*_*hik 8

在 C++ 中,从函数返回对象会在调用者上下文中复制构造该对象,然后在返回的函数中销毁复制的对象。在某些情况下,可以删除此副本。

       Counter c;
       return c;
Run Code Online (Sandbox Code Playgroud)

这称为返回值优化,在这种情况下不是强制性的。此处允许复制省略,但它是可选的。您使用的编译器之一会删除此副本,而另一个则不会。

如果没有复制省略,编译器将在调用者的上下文中复制构造返回的对象。

Counter缺少复制构造函数,因此显示的代码无法记录复制构造对象的实例。

只需添加一个复制构造函数:

class Counter {
public:
    Counter() {
        counter++;
    }
    Counter(const Counter &) {
        counter++;
    }
    ~Counter() {
        counter--;
    }
};
Run Code Online (Sandbox Code Playgroud)

现在,无论有或没有复制省略,您都将获得预期的结果。

如果您在复制构造函数中设置断点,您将在从函数返回时看到断点命中(当使用不消除复制的编译器时)。

  • 还值得一提的是,在编译时添加“-fno-elide-constructors”将在两个编译器上给出一致的结果。这是了解正在发生的事情的另一种方式。 (2认同)