函数返回的 类 上的析构函数
Destructors on classes returned by functions
我有以下代码:
#include <stdio.h>
class Foo {
public:
int a;
~Foo() { printf("Goodbye %d\n", a); }
};
Foo newObj() {
Foo obj;
return obj;
}
int main() {
Foo bar = newObj();
bar.a = 5;
bar = newObj();
}
当我用 g++
和 运行 编译时,我得到:
Goodbye 32765
Goodbye 32765
打印的数字似乎是随机的。
我有两个问题:
- 为什么析构函数被调用了两次?
- 为什么
5
不是第一次打印?
我有 C 语言背景,因此有 printf
,我无法理解析构函数,它们何时被调用以及如何从函数返回 class。
1) 很简单,您的代码中有两个 Foo
对象(在 main
和 newObj
中),所以有两个析构函数调用。实际上,这是您将看到的最少析构函数调用次数,编译器可能会为 return 值创建一个未命名的临时对象,如果这样做了,您将看到三个析构函数调用。 return 值优化 的规则在 C++ 的历史中发生了变化,因此您可能会也可能不会看到这种行为。
2) 因为在调用析构函数时 Foo::a
的值永远不会是 5,所以它在 newObj
中永远不会是 5,尽管在 main
中它是 5 但它不是当你到达 main
的末尾时(即调用析构函数时)。
我猜你的误解是你认为赋值语句bar = newObj();
应该调用析构函数,但事实并非如此。在赋值过程中,对象会被覆盖,但不会被销毁。
让我们看看你的主要功能发生了什么:
int main() {
Foo bar = newObj();
这里我们只是实例化一个Foo
并用newObj()
的return值初始化它。这里没有调用析构函数是因为copy elision:总结的很快,不是把copying/movingobj
变成bar
然后析构obj
,obj
直接在bar
的存储中构建。
bar.a = 5;
这里没什么好说的。我们只需将 bar.a
的值更改为 5。
bar = newObj();
这里bar
是copy-assigned1newObj()
的returned值,那么这个创建的临时对象函数调用被析构2,这是第一个Goodbye
。此时 bar.a
不再是 5
而是临时对象的 a
.
中的任何内容
}
main()
结束,局部变量被析构,包括bar
,这是第二个Goodbye
,因为之前赋值,所以没有打印5
。
1 由于 user-defined 析构函数,这里没有移动赋值,没有隐式声明移动赋值运算符。
2 正如 YSC 在评论中提到的,请注意此析构函数调用具有未定义的行为,因为它正在访问此时未初始化的 a
。 bar
与临时对象的赋值,特别是作为其中一部分的 a
的赋值,出于相同的原因也具有未定义的行为。
我认为这里的主要混淆之一是对象标识。
bar
总是 同一个对象。当您将不同的对象分配给 bar
时,您不会破坏第一个 - 您调用 operator=(const& Foo)
(复制赋值运算符)。它是编译器可以 auto-generated 的 five special member functions 之一(在这种情况下是这样),只是 覆盖 bar.a
任何东西在 newObj().a
。提供您自己的 operator=
以查看 that/when 发生这种情况(并确认 a
确实是 5
发生之前)。
bar
的析构函数仅被调用一次 - 当 bar
在函数末尾超出范围时。只有一个其他析构函数调用 - 由第二个 newObj()
编辑的临时 return。来自 newObj()
的第一个临时变量被省略(在这种情况下语言允许它,因为创建和立即销毁它从来没有真正的意义)并直接用 return 值初始化 bar
newObj()
.
我有以下代码:
#include <stdio.h>
class Foo {
public:
int a;
~Foo() { printf("Goodbye %d\n", a); }
};
Foo newObj() {
Foo obj;
return obj;
}
int main() {
Foo bar = newObj();
bar.a = 5;
bar = newObj();
}
当我用 g++
和 运行 编译时,我得到:
Goodbye 32765
Goodbye 32765
打印的数字似乎是随机的。
我有两个问题:
- 为什么析构函数被调用了两次?
- 为什么
5
不是第一次打印?
我有 C 语言背景,因此有 printf
,我无法理解析构函数,它们何时被调用以及如何从函数返回 class。
1) 很简单,您的代码中有两个 Foo
对象(在 main
和 newObj
中),所以有两个析构函数调用。实际上,这是您将看到的最少析构函数调用次数,编译器可能会为 return 值创建一个未命名的临时对象,如果这样做了,您将看到三个析构函数调用。 return 值优化 的规则在 C++ 的历史中发生了变化,因此您可能会也可能不会看到这种行为。
2) 因为在调用析构函数时 Foo::a
的值永远不会是 5,所以它在 newObj
中永远不会是 5,尽管在 main
中它是 5 但它不是当你到达 main
的末尾时(即调用析构函数时)。
我猜你的误解是你认为赋值语句bar = newObj();
应该调用析构函数,但事实并非如此。在赋值过程中,对象会被覆盖,但不会被销毁。
让我们看看你的主要功能发生了什么:
int main() {
Foo bar = newObj();
这里我们只是实例化一个Foo
并用newObj()
的return值初始化它。这里没有调用析构函数是因为copy elision:总结的很快,不是把copying/movingobj
变成bar
然后析构obj
,obj
直接在bar
的存储中构建。
bar.a = 5;
这里没什么好说的。我们只需将 bar.a
的值更改为 5。
bar = newObj();
这里bar
是copy-assigned1newObj()
的returned值,那么这个创建的临时对象函数调用被析构2,这是第一个Goodbye
。此时 bar.a
不再是 5
而是临时对象的 a
.
}
main()
结束,局部变量被析构,包括bar
,这是第二个Goodbye
,因为之前赋值,所以没有打印5
。
1 由于 user-defined 析构函数,这里没有移动赋值,没有隐式声明移动赋值运算符。
2 正如 YSC 在评论中提到的,请注意此析构函数调用具有未定义的行为,因为它正在访问此时未初始化的 a
。 bar
与临时对象的赋值,特别是作为其中一部分的 a
的赋值,出于相同的原因也具有未定义的行为。
我认为这里的主要混淆之一是对象标识。
bar
总是 同一个对象。当您将不同的对象分配给 bar
时,您不会破坏第一个 - 您调用 operator=(const& Foo)
(复制赋值运算符)。它是编译器可以 auto-generated 的 five special member functions 之一(在这种情况下是这样),只是 覆盖 bar.a
任何东西在 newObj().a
。提供您自己的 operator=
以查看 that/when 发生这种情况(并确认 a
确实是 5
发生之前)。
bar
的析构函数仅被调用一次 - 当 bar
在函数末尾超出范围时。只有一个其他析构函数调用 - 由第二个 newObj()
编辑的临时 return。来自 newObj()
的第一个临时变量被省略(在这种情况下语言允许它,因为创建和立即销毁它从来没有真正的意义)并直接用 return 值初始化 bar
newObj()
.