我对按值传递对象时到底会发生什么以及复制构造函数的工作没有什么困惑。为了实践这个概念,我编写了以下代码。
#include <iostream>
using namespace std;
class Cat
{
public:
Cat();
Cat(Cat&);
~Cat() { cout << "Destructor calledn";}
int itsage;
};
Cat::Cat()
{
cout << "Constructor calledn";
itsage=2;
}
Cat::Cat(Cat& )
{
cout << "Copy constructor calledn";
itsage=50;
}
Cat myFunction(Cat Frisky)
{
cout << "Frisky's age: " << Frisky.itsage << "n";
Frisky.itsage=100;
cout << "Reassigned Frisky's age: "<< Frisky.itsage << "n";
return Frisky;
}
int main()
{
Cat Mani;
cout << "Mani's age: " << Mani.itsage << "n";
myFunction(Mani);
cout << Mani.itsage;
cout << endl;
return 0;
}
我得到的输出为:
Constructor called
Mani's age: 2
Copy constructor called
Frisky's age: 50
Reassigned Frisky's age: 100
Copy constructor called
Destructor called
Destructor called
2
Destructor called
我已经理解了输出的前六行。我读过,当你传递或返回物体时根据值,将生成对象的临时副本。因此,Frisky
的临时副本在执行语句return Frisky
时创建。但由于没有分配给任何东西我认为这产生了对析构函数进行第一次调用的第七行输出。第九行的输出十对我来说也很清楚。我对输出的第八行感到困惑,它对析构函数进行了第二次调用。
疑问1:在对析构函数的第二次调用中究竟销毁了什么?当我们通过语句myFunction(Mani)
调用myFunction时,Mani
的本地副本被创建。复制构造函数完成后,在myFunction
中也创建了Frisky
。现在,当myFunction
结束时,什么被摧毁了?Mani
或Frisky
的本地副本?
疑问2:对象的本地副本存储在哪里?意味着当我调用myFunction
时,Mani
的本地副本存储在哪里?在myFunction
内部,在main
内部还是其他地方?
怀疑3:复制构造函数也是一种函数。然后在头中,在参数列表中,我们只提到了参数类型Cat &
,但没有写参数名称。我读到过,在函数原型中不写参数名称是可以的,但当我们写函数定义时,我们应该同时写类型和名称。Copy构造函数是否属于此规则的异常?
主要问题是;复制";构造函数Cat::Cat(Cat &)
不复制itsage
的值,而是将其设置为任意值。因此,按值传递Cat
对象并返回它将产生奇怪的结果,因为接收到的对象与原始对象不相似。
详细信息:函数myFunction
的参数Frisky
通过值传递。这意味着调用CCD_;变成";Frisky
。副本将由副本构造函数Cat::Cat(Cat &)
构造。由于此构造函数将itsage
设置为50
,因此myFunction
总是报告50
的年龄,而不管原始传递的值是多少。
这同样适用于返回值:从myFunction
返回对象会使用复制构造函数Cat::Cat(Cat &)
为调用方构造一个副本。返回的副本始终具有值itsage=50
。
解决方案:编写一个正确的复制构造函数:
Cat::Cat(const Cat &other)
: itsage(other.itsage)
{ }
或者更好:只需省略复制构造函数即可使用默认构造函数。这个默认构造函数基本上与上面提到的相同,并且是免费的。
如果您真的希望修改传递给myFunction
的值,并在myFunction
之外看到这些修改,则需要通过引用传递参数Frisky
:
Cat myFunction(Cat &Frisky)
{
// ...
}
解答您的疑虑:
第一个析构函数调用来自参数
Frisky
,当函数myFunction
返回时,该函数将结束其生存期。第二个析构函数调用来自返回的对象。由于它从未被使用过,它在调用myFunction
之后立即被销毁。第三个析构函数调用是在main
结束时Mani
的生存期结束。就在进入
myFunction
之前,Mani
的本地副本存储在堆栈上,并且可以由Frisky
在myFunction
内部引用。它将在myFunction
结束时被销毁。是的,您可以省略参数的名称,但在复制构造函数的情况下,这是无稽之谈。复制构造函数应该构造一个与传递的对象类似的对象。从那以后,它需要访问传递对象内部的状态。
生存期:下图显示了类Cat
:的各种对象的生存期
int main()
{ Mani
Cat Mani; Frisky -+-
myFunction(Mani) -+- |
Cat myFunction(Cat Frisky) | |
{ | |
Frisky.itsage=100; Return | |
return Frisky; -+- | |
} | | |
-(*)----------------------------------------------+- | |
;-(*)--------------------------------------------------------+- |
return 0; |
}-(*)---------------------------------------------------------------------+-
(*)
大致表示调用其析构函数的时刻。
您问,为什么Mani
的本地副本没有析构函数调用。创建Mani
的唯一本地副本(我不完全确定,你的意思是什么(是为了在函数myFunction
中获得一个名为Frisky
的对象。您不需要Mani
的任何其他副本。