查询对象的本地副本和复制构造函数的工作

Query about local copy of object and working of copy constructor

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

#include <iostream>
 using namespace std; 
 class Cat
{
 public:
  Cat();
  Cat(Cat&);
  ~Cat() { cout << "Destructor called\n";}
  int itsage;
};
 
 Cat::Cat()
{
 cout << "Constructor called\n";
 itsage=2;
}
 
 Cat::Cat(Cat& )
{
 cout << "Copy constructor called\n";
 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

前六行输出我已经看懂了。我读过当你传递或 return 对象时 按值,创建对象的临时副本。所以, Frisky 的临时副本得到了 在执行语句 return Frisky 时创建。但它立即被销毁,因为它没有分配给 任何事物。我认为这是第一次调用析构函数时产生的第七行输出。第九行的输出 十对我来说也很清楚。我对第二次调用析构函数的第八行输出感到困惑。

疑问1:第二次调用析构函数时,到底销毁了什么?当我们通过语句 myFunction(Mani) 调用 myFunction 时,Mani 的本地副本 被创建。复制构造函数完成后,Frisky 也在 myFunction 中创建。现在当 myFunction 结束时,什么被摧毁了? ManiFrisky?

的本地副本

疑问2:这个对象的本地副本存储在哪里?意思是当我调用 myFunction 时,Mani 的本地副本存储在哪里?在 myFunctionmain 或其他地方?

疑问3:拷贝构造函数也是函数的一种。然后在header中,在参数列表中,我们只提到了参数类型Cat &而没有写参数名。我读到在函数原型中不写参数名是可以的,但是当我们在写函数定义时,我们应该写类型和名称。复制构造函数是此规则的例外吗?

主要问题是,“复制”构造函数 Cat::Cat(Cat &) 不会复制 itsage 的值,而是将其设置为任意值。因此,按值传递 Cat 对象并 returning 它会产生奇怪的结果,因为接收到的对象与原始对象不相似。

详细说明:函数myFunction的参数Frisky是按值传递的。这意味着调用 myFunction 会将传递的对象“复制到”Frisky。副本将由复制构造函数Cat::Cat(Cat &)构造。由于此构造函数将 itsage 设置为 50,因此 myFunction 始终报告 50 的年龄,而不管原始传递值如何。

这同样适用于 return 值:从 myFunction 返回对象会使用复制构造函数 Cat::Cat(Cat &) 为调用者构造一个副本。 returned 副本始终具有值 itsage=50

解决方法:写一个合适的拷贝构造函数:

Cat::Cat(const Cat &other)
  : itsage(other.itsage)
{ }

甚至更好:通过简单地省略复制构造函数来使用默认值。这个默认构造函数与上面提到的基本相同,而且是免费提供的。

如果你真的想修改传递给myFunction的值并在myFunction之外看到这些修改,你需要通过引用传递参数Frisky

Cat myFunction(Cat &Frisky)
{
    // ...
}

解答你的疑惑:

  1. 第一个析构函数调用来自参数 Frisky,它在函数 myFunction return 时结束其生命周期。第二个析构函数调用来自 returned 对象。因为它永远不会被使用,所以它在 myFunction 调用后立即被销毁。第三次析构函数调用是在main.

    结束时结束Mani的生命周期
  2. 就在进入 myFunction 之前,Mani 的本地副本存储在堆栈中,可以在 myFunction 中被 Frisky 引用。将在myFunction.

    结束时销毁
  3. 是的,您可以省略参数的名称,但在复制构造函数的情况下,这是无稽之谈。复制构造函数应该构造一个类似于传递的对象的对象。因为它需要访问传递的对象内的状态。

Lifetime:下图展示了classCat:

各个对象的生命周期
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.

的任何其他副本