返回在循环内创建的变量导致析构函数被调用两次

Returning variable created inside the loop causes destructor to be called twice

我想了解 C++ 标准对 how/when 当从函数返回对象时应该调用析构函数的内容 - 考虑这个简单的结构和两个函数 -

#include <iostream>
int g = 0;
struct foo {
    int myid;
    foo() {
        myid = g;
        g++;
        std::cout << "Created " << myid << std::endl;
    }
    ~foo() {
        std::cout << "Destroyed " << myid << std::endl;
    }
};

foo bar(void) {
    int i = 0;
    for (foo s; i < 10; i++) {
        if (i == 5)
            return s;
    }
}
foo bar2(void) {
    int i = 0;
    foo s;
    for (; i < 10; i++) {
        if (i == 5)
            return s;
    }
}

int main() {
    bar();
    bar2();
    return 0;
}

我正在尝试跟踪调用析构函数的次数。上述程序的输出是 -

Created 0
Destroyed 0
Destroyed 0
Created 1
Destroyed 1

我能理解bar2的行为。一个对象被创建一次并被销毁(我相信析构函数是从 main 调用的)。但是在bar的时候对象是在循环内部声明的。它使析构函数被调用两次。造成这种差异的原因是什么?

标准是否将这种行为留给了实现(因为复制省略?)而 g++ 只是为这两种情况选择了这种行为?如果是这样,我该如何编写此函数才能获得可预测的行为。我需要调用析构函数的次数与构造函数的调用次数完全相同(最好以相反的顺序)。只要构造函数也被调用两次,我就可以接受两次调用析构函数。原因是因为我在构造函数中分配一些数据并在析构函数中释放它。

添加此代码

foo(const foo& rhs) {
    myid = g;
    g++;
    std::cout << "Created from copy " << myid << std::endl;
}

这是一个复制构造函数,它也被调用了,只是你没有意识到它,因为你使用的是默认版本,它显然不会打印任何内容,也不会增加你的计数器。

cppinsights 告诉你发生了什么:有一个默认的复制构造函数被调用,所以一个副本也被破坏了。

但是,这两个对象都经过命名 return 值优化,copy elision that elides the copy constructor. If you compile and run your code with clang, that is indeed the case (https://godbolt.org/z/KWhRpL 的变体没有双 "Destroyed").

NRVO 是可选的,而且 gcc 似乎没有在那里应用它。没有办法强制 NRVO 发生,但您可以实现一个将被调用的移动构造函数。