查询对象的本地副本和副本构造函数的工作情况



我对按值传递对象时到底会发生什么以及复制构造函数的工作没有什么困惑。为了实践这个概念,我编写了以下代码。

#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结束时,什么被摧毁了?ManiFrisky的本地副本?

疑问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)
{
// ...
}

解答您的疑虑:

  1. 第一个析构函数调用来自参数Frisky,当函数myFunction返回时,该函数将结束其生存期。第二个析构函数调用来自返回的对象。由于它从未被使用过,它在调用myFunction之后立即被销毁。第三个析构函数调用是在main结束时Mani的生存期结束。

  2. 就在进入myFunction之前,Mani的本地副本存储在堆栈上,并且可以由FriskymyFunction内部引用。它将在myFunction结束时被销毁。

  3. 是的,您可以省略参数的名称,但在复制构造函数的情况下,这是无稽之谈。复制构造函数应该构造一个与传递的对象类似的对象。从那以后,它需要访问传递对象内部的状态。

生存期:下图显示了类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的任何其他副本。

最新更新