返回在循环内创建的变量导致析构函数被调用两次
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 发生,但您可以实现一个将被调用的移动构造函数。
我想了解 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 发生,但您可以实现一个将被调用的移动构造函数。