了解 C++ 中按值传递对象时的复制构造函数和临时对象创建

Understanding Copy Constructor and Temporary Object Creation in C++ When Passing Objects by Value

提问人:Yarin0600 提问时间:8/25/2023 更新时间:8/25/2023 访问量:74

问:

给定下一个代码:

#include <iostream>
#include <vector>
#include <cstring>

class Person
{
public:
   Person(const char *i_Name = "Unknown", int i_Age = 0)
       : m_Name(copyFromTo(i_Name, m_Name)), m_Age(i_Age)
   {
      std::cout << "creating obj " << this << ".\n";
   }
   Person(const Person &other)
   {
      std::cout << "Copying from " << &other << " to " << this << ".\n";
      this->m_Name = copyFromTo(other.m_Name, this->m_Name);
      this->setAge(other.m_Age);
   }
   ~Person()
   {
      std::cout << "destroying obj " << this << ".\n";
      if (m_Name)
      {
         delete[] m_Name;
      }
   }
   void printDetails() { std::cout << "Hello, my name is " << m_Name << " and I am " << m_Age << " years old.\n"; }
   void setAge(int i_Age) { m_Age = i_Age; }
   int getAge() const { return m_Age; }

private:
   char *copyFromTo(const char *i_Source, char *i_Destination)
   {
      int sourceSize = strlen(i_Source);
      i_Destination = new char[sourceSize + 1];
      memcpy(i_Destination, i_Source, sourceSize);
      i_Destination[sourceSize] = '\0';
      return i_Destination;
   }
   char *m_Name;
   int m_Age;
};

Person birthday(Person person)
{
   std::cout << "person's address inside the function " << &person << ".\n";
   person.setAge(person.getAge() + 1);
   return person;
}

int main()
{
   Person person = {"New Person", 24};
   birthday(person);
   std::cout << "END OF PROGRAM.\n";
}

下一个输出:

输出:

creating obj 0x61fea8.
Copying from 0x61fea8 to 0x61feb8.
person's address inside the function 0x61feb8.
Copying from 0x61feb8 to 0x61feb0. // [temporary object creation]
destroying obj 0x61feb0. // [temporary object destruction]
destroying obj 0x61feb8.
END OF PROGRAM.
destroying obj 0x61fea8.

请注意,程序会在我们输入函数 'birthday' 后立即创建一个对象,因为参数 'person' 是按值传递的。但是,在函数的末尾,还有对复制构造函数的另一次调用,我们正在创建一个额外的临时对象,然后立即销毁该对象。如果我们在销毁本地“person”对象之前销毁它,为什么还需要这个临时对象,而不是 “birthday” 函数中的本地 'person' 对象。

谢谢。

我尝试了谷歌,看到在函数结束之前创建了一个临时对象,但我不明白如果我甚至在销毁函数的第一个本地对象之前就销毁了它,为什么还需要它。它实际上是用来做什么的?

C++ 析构函数 构造函数

评论

1赞 273K 8/25/2023
if I destroy it用这个新对象初始化一个变量,它不会被销毁。该函数承诺返回一个副本,它完全做到了。副本被销毁,因为您不使用它。
2赞 cigien 8/25/2023
将结果分配给变量,您将看到在函数返回后副本被销毁。birthday
0赞 Louis Go 8/25/2023
有两个临时的。一个是争论,另一个是生日的回归。你没有抓住回报,所以它也会立即被摧毁。birthday
2赞 Sam Varshavchik 8/25/2023
现在,您将了解复制省略和返回值优化的所有奇迹,这些奇迹因 C++ 版本而异,在某些情况下,由于编译器的奇思妙想,可能会也可能不会发生。欢迎使用 C++。
2赞 Pete Becker 8/25/2023
次要观点:虽然你的代码似乎不允许是空指针,但一般来说,在删除它之前,你不需要测试它是否是空指针。在析构函数中就足够了; 知道 null 指针。m_namedelete [] m_name;delete

答:

0赞 Klaus 8/25/2023 #1

如果将 main 函数修改为:

int main()
{
   Person person = {"New Person", 24}; 
   //birthday(person);
   Person anotherPerson = birthday(person);
   std::cout << "anotherPerson's address in main " << & anotherPerson << std::endl;
   std::cout << "END OF PROGRAM.\n";
}

您将看到“第二个临时”的地址将与局部变量相同。anotherPerson

会发生什么: 如果函数必须返回一个值,则该值必须在函数离开其作用域后有效。当前的 C++ 保证这些副本将被省略,并且局部变量的“新”位置(此处在 的作用域中)将用于此“副本”,实际上它不再是副本。这种优化称为复制省略main

有趣的是,未使用的返回值也会产生未使用的临时值。但这不是 C++ 标准告诉我们的任何事情。这只是对您正在使用的编译器的观察。

顺便说一句:字符串的初始化和副本已完全损坏,并且该变量在未初始化的情况下使用!只需启用所有警告,不要试图变得更好,因为标准行为已经是!std::string

评论

0赞 user17732522 8/26/2023
"有趣的是,未使用的返回值也会产生未使用的临时值。但这不是 C++ 标准告诉我们的任何事情。这只是对您正在使用的编译器的观察结果“:否,此行为已指定,编译器必须遵循它。