函数返回的类的析构函数

时间:2019-06-14 05:45:12

标签: c++ class g++ destructor

我有以下代码:

#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

打印的数字似乎是随机的。

我有两个问题:

  1. 为什么析构函数被调用两次?
  2. 为什么5第一次不打印?

我来自C语言,因此是printf,而且在理解析构函数,何时调用析构函数以及如何从函数返回类方面遇到困难。

3 个答案:

答案 0 :(得分:11)

让我们看看您的主要功能发生了什么:

int main() {
    Foo bar = newObj();

在这里,我们只实例化一个Foo并使用返回值newObj()对其进行初始化。由于copy elision在这里没有调用析构函数:总结起来非常迅速,而不是将obj复制/移动到bar然后解构objobj是直接在bar的存储中构建。

    bar.a = 5;

这里无话可说。我们只是将bar.a的值更改为5。

    bar = newObj();

在这里bar被复制分配了 1 返回值newObj(),然后销毁了由该函数调用创建的临时对象 2 ,这是第一个Goodbye。此时,bar.a不再是5,而是临时对象的a中的任何内容。

}

main()的末尾,局部变量被破坏,包括bar,这是第二个Goodbye,由于先前的赋值,它不打印5


1 由于用户定义了析构函数,因此未发生移动分配,因此未隐式声明任何移动分配运算符。
2 正如YSC在评论中提到的那样,请注意,此析构函数调用具有未定义的行为,因为它正在访问a,该bar在这一点上尚未初始化。出于相同的原因,a与临时对象的分配,尤其是info Starting JS server... info Building and installing the app on the device (cd android && gradlew.bat app:installDebug)... > Task :app:compileDebugJavaWithJavac error: error reading C:\Users\Joshua Uyi\.m2\repository\javax\inject\javax.inject\1\javax.inject-1.jar; error in opening zip file error: error reading C:\Users\Joshua Uyi\.m2\repository\javax\inject\javax.inject\1\javax.inject-1.jar; error in opening zip file > Task :app:checkDebugDuplicateClasses FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:checkDebugDuplicateClasses'. > 1 exception was raised by workers: java.util.zip.ZipException * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0. Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/5.4.1/userguide/command_line_interface.html#sec:command_line_warnings BUILD FAILED in 15s 18 actionable tasks: 18 executed error Could not install the app on the device, read the error above for details. Make sure you have an Android emulator running or a device connected and have set up your Android development environment: https://facebook.github.io/react-native/docs/getting-started.html error Command failed: gradlew.bat app:installDebug. Run CLI with --verbose flag for more details. 作为其一部分的分配,也具有未定义的行为。

答案 1 :(得分:2)

1)很简单,您的代码中有两个Foo对象(在mainnewObj中),所以有两个析构函数调用。实际上,这是您将看到的最小析构函数调用数,编译器可能会为返回值创建一个未命名的临时对象,如果这样做,您将看到三个析构函数调用。在C ++的历史中,返回值优化的规则已更改,因此您可能会或可能不会看到此行为。

2)因为调用析构函数时Foo::a的值从不为5,所以newObj中的值从不为5,尽管main中的值为5,但不是时间到main的结尾(调用析构函数的时间)。

我猜您的误解是您认为赋值语句bar = newObj();应该调用析构函数,但事实并非如此。在分配过程中,对象将被覆盖,但不会被破坏。

答案 2 :(得分:0)

我认为这里的主要困惑之一是对象身份。

bar总是 同一对象。将不同的对象分配给bar时,不会破坏第一个对象,而是调用operator=(const& Foo)(副本分配运算符)。它是five special member functions之一,可以由编译器自动生成(在这种情况下就是这种情况),并且仅用{{1}中的任何内容覆盖 bar.a }。提供您自己的newObj().a来查看/何时发生(并在发生此情况之前确认operator=确实是a)。

5的析构函数仅被调用一次-当bar在函数末尾超出范围时。只有一个析构函数调用-第二个bar返回的临时函数。删除newObj()中的第一个临时文件(在这种情况下,该语言允许使用该语言,因为实际上并没有必要创建和立即销毁它),并使用返回值{{1 }}。