(我的问题的答案涉及复制构造函数,但复制发生在从函数返回时,而不是在对另一个类的方法调用中。我实际上看到引用的可能重复,但没有从副本中推断出vector :: push_back我的函数也在这里复制了。也许我应该有。)
我试图理解自动对象的构造/破坏。我遇到了一些看起来很可疑的代码,所以我编写了自己的版本以努力理解它。简而言之,原始代码包含一个返回函数本地对象的函数(自动)。这对我来说看起来不安全,所以我写了这个程序来探索它:
n
我得到了这个输出:
Phantom 1 constructed. Phantom 2 constructed. Phantom 2 destructed. Phantom 2 destructed. Phantom 2 speaks.
输出中的第四行让我感到困惑。
在输入#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。
在输入main
时自动构建幻像2。
当退出getPhantom
时,幻像2会被自动销毁(这就是为什么我认为从getPhantom
返回它是不安全的。)
但在那之后我很困惑。根据调试器,getPhantom
在输出第四行之前返回。当第二次调用getPhantom
的析构函数时,调用堆栈是:
main ~Phantom
在托管语言中,我可以看到这一行:
Phantom
会破坏Phantom 1,但它不会触及Phantom 2.这是C ++,而不是Java。
第二次调用Phantom 2的析构函数是什么原因?
答案 0 :(得分:7)
您返回一份副本。因此,getPhantom()
中的变量在作用域的末尾被销毁,并且你的副本也是id 2。这是因为在返回时它调用了不增加id的复制构造函数(也是默认的)
答案 1 :(得分:5)
您忘记了正确说明:
复制构造函数。
作业运营商。
在这两种情况下,您将使用多个具有相同id
的对象,最终两个对象在其析构函数中打印相同的id
。在复制构造函数的情况下,构造函数中不会打印任何消息,因为您没有定义自己的复制构造函数。对于赋值运算符,构造函数中分配的id
将被另一个对象的重复id
覆盖。这就是这里发生的事情:
phantom = getPhantom();
因此,您的会计错误。
答案 2 :(得分: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;
}
答案 3 :(得分:2)
不要质疑这样的简单代码是否导致破坏一个从未构造过的对象,或者两次破坏某些东西,而是考虑到构造 对象的可能性更大,而且每个对象都是只销毁一次,但你没有准确地跟踪建筑和破坏。
现在想一想用C ++构造对象的其他方法,并考虑如果在任何时候使用复制构造函数会发生什么。然后考虑如何从函数返回本地对象,以及是否使用了复制构造函数。
如果要改进测试代码,请在析构函数中打印出this
指针的值,并且您将看到您尝试为每个对象提供ID是有缺陷的。您有多个具有不同身份的对象(即内存中的地址),但相同的&#34; ID&#34;。
答案 4 :(得分:2)
Phantom autoPhantom;
返回autoPhantom; //这可以安然
非常安全。该函数按值返回对象,即将生成并返回一个副本(可能由&#34;返回值优化&#34;(RVO))删除。
如果函数返回了一个引用或指向局部变量的指针,那么你就是对的,这将是不安全的。
&#34;额外&#34;析构函数调用只是破坏局部变量,然后销毁返回的副本。
答案 5 :(得分:1)
将此类代码添加到您的班级:
Phantom& operator=(const Phantom& inPhantom)
{
printf("Assigning.\n");
}
并且您将看到第二个对象未被销毁两次。这种探索更为简单。在赋值操作时,第一个对象将其所有字段值更改为第二个对象的值,但不会将其销毁。它仍然是第一对象。 您更新的示例:http://cpp.sh/6b4lo