3

(我的问题的答案涉及复制构造函数,但是复制发生在从函数返回时,而不是在对另一个类的方法调用中。我实际上看到了引用的可能重复项,但没有从 vector 制作的副本中推断出:: push_back 我这里的函数也做了一个副本。也许我应该有。)

我试图了解自动对象的构造/破坏。我遇到了一些对我来说很可疑的代码,所以我编写了自己的版本来努力理解它。简而言之,原始代码包含一个函数,该函数返回该函数的本地对象(自动)。这对我来说看起来不安全,所以我编写了这个程序来探索它:

#include <stdio.h>

class Phantom
{
private:
    static int counter;
    int id;

public:
    Phantom()
    {
        ++counter;
        id = counter;
        printf("Phantom %d constructed.\n", id);
    };

    virtual ~Phantom()
    {
        printf("Phantom %d destructed.\n", id);
    };

    void speak()
    {
        printf("Phantom %d speaks.\n", id);
    };
};

int Phantom::counter = 0;

Phantom getPhantom()
{
    Phantom autoPhantom;

    return autoPhantom; // THIS CAN'T BE SAFE
}

int main()
{
    Phantom phantom;

    phantom = getPhantom();

    phantom.speak();

    return 0;
}

我得到这个输出:

幻影 1 构建。
幻影 2 构建。
幻影2被毁。
幻影2被毁。
幻影 2 会说话。

这是输出中的第四行让我感到困惑。

Phantom 1 是在main输入时自动构建的。

Phantom 2 是在getPhantom输入时自动构建的。

Phantom 2 在getPhantom退出时会自动销毁(这就是为什么我认为返回它getPhantom是不安全的)。

但在那之后我很困惑。根据调试器,在第四行输出出现之前getPhantom已经返回。第二次调用 's 的析构函数时,调用栈是这样的:Phantom

主要的
~幻影

在托管语言中,我可以看到这一行:

phantom = getPhantom();

会破坏 Phantom 1,但不会触及 Phantom 2。这是 C++,而不是 Java。

是什么导致第二次调用 Phantom 2 的析构函数?

4

6 回答 6

8

你退回一份。因此,变量 ingetPhantom()在作用域的末尾被销毁,剩下的副本也具有 id 2。这是因为在返回时它调用不会增加 id 的复制构造函数(也是默认构造函数)。

于 2016-07-20T12:42:41.270 回答
5

您忘记正确说明:

  1. 复制构造函数。

  2. 赋值运算符。

在这两种情况下,您最终都会得到多个具有相同id的对象,两个对象最终id在它们的析构函数中打印相同的。在复制构造函数的情况下,构造函数中不会打印任何消息,因为您没有定义自己的复制构造函数。在赋值运算符的情况下,构造函数中的赋值会被另一个对象id的副本覆盖。id这就是这里发生的事情:

phantom = getPhantom();

因此,您的会计会出错。

于 2016-07-20T12:42:58.933 回答
3

我将评论您对返回具有自动存储的对象不安全的担忧:

Phantom getPhantom()
{
    Phantom autoPhantom;

    return autoPhantom; // THIS CAN'T BE SAFE
}

如果这不安全,那么 C++ 将毫无用处,你不觉得吗?要查看我在说什么,只需将类型替换为...说int

int getPhantom()
{
    int autoPhantom = 0;

    return autoPhantom; // How does this look to you now?
}

需要明确的是:这是非常安全的,因为您正在返回值(即对象的副本)。

不安全的是返回对此类对象的指针或引用:

int* getInt()
{
   int a = 0;
   return &a;
}
于 2016-07-20T12:51:32.877 回答
2

与其质疑这样简单的代码是否会导致破坏从未构造过的对象,还是会破坏两次,不如考虑更可能的是该对象构造并且每个对象仅被破坏一次,但您没有跟踪构造和破坏准确。

现在考虑在 C++ 中可以构造对象的其他方式,并考虑如果在任何时候使用复制构造函数会发生什么。然后考虑如何从函数返回本地对象,以及是否使用了复制构造函数。

如果你想改进你的测试代码,在析构函数中打印出this指针的值,你会发现你给每个对象一个 ID 的尝试是有缺陷的。您有多个具有不同身份(即内存中的地址)但“ID”相同的对象。

于 2016-07-20T12:43:59.250 回答
2

幻影自动幻影;

返回自动幻影;// 这不安全

这是完全安全的。该函数按值返回对象,也就是说,将制作并返回一个副本(可能被“返回值优化”(RVO)省略)。

如果函数返回了对局部变量的引用或指针,那么你是对的,而且它是不安全的。

“额外”析构函数调用的原因仅仅是局部变量被破坏,然后返回的副本被破坏。

于 2016-07-20T12:46:46.307 回答
1

将这样的代码添加到您的课程中:

Phantom& operator=(const Phantom& inPhantom)
{
    printf("Assigning.\n");
}

你会看到第二个对象没有被破坏两次。解释比较简单。在赋值操作中,第一个对象将其所有字段值更改为第二个对象的值,但它不会被销毁。它仍然是第一个对象。您更新的示例:http ://cpp.sh/6b4lo

于 2016-07-20T13:06:59.290 回答