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



当函数返回对象时,C++标准对如何/何时调用析构函数有什么规定考虑一下这个简单的结构和两个函数-

#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;
}

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

cppings.com告诉您发生了什么:有一个默认的副本构造函数被调用,因此副本也被销毁。

然而,在那里,这两个对象都要经过命名的返回值优化,这是复制省略的一种变体,省略了复制构造函数。如果您使用clang编译和运行代码,那么情况确实如此(https://godbolt.org/z/KWhRpL没有双重"毁灭"(。

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

最新更新