半小时掌握C++之临时对象

125 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情

本文主要总结了在C++开发过程中所遇到的临时对象产生的相关问题,三种常见的临时对象创建的情况:

类型转换

函数传对象

函数返回对象

类的结构

#include <iostream>
using namespace std;
class A 
{
public:
   int a;
   int b;
public:
   A(int t1 = 0,int t2 = 0){
      a = t1;
      b = t2;
      cout << "类A的有参构造被调用" << endl;
   }
   ~A(){
      cout << "类A的析构函数被调用" << endl;
   }
   A(const A& tmp){
      cout << "类A的拷贝构造函数被调用" << endl;
      a = tmp.a;
      b = tmp.b;
      cout << "this:" <<this<< endl;
   }
   A& operator=(const A& tmp){
      cout << "类A的拷贝赋值运算符" << endl;
      a = tmp.a;
      b = tmp.b;
      cout << "this:" <<this<< endl;
      return *this;     
   }
};

类型转换

int main()
{
   cout << "-----------" << endl;
   A peach;
   peach = 100;
   cout << "peach的地址:" <<&peach<< endl;
   cout << "-----------" << endl;
   return 0;
}
/*
-----------
类A的有参构造被调用
类A的有参构造被调用
类A的拷贝赋值运算符
this:0x7ffedafcf978
类A的析构函数被调用
peach的地址:0x7ffedafcf978
-----------
类A的析构函数被调用
*/

peach = 100;发生了隐式类型转换,将100来构造一个A对象

用explicit来禁止隐式类型转换

将100赋给peach,而peach本身是个A类型的对象,100是个数字,编译器帮助我们以100为参数调用了类A的构造函数创建了一个临时对象,因为A构造函数的两个参数都有默认值,所以这里的100就代替了第一个参数,所以从100是可以成功创建出A类对象的。

产生临时对象需要花时间,耗费性能,我们要避免产生临时对象。

改进

int main()
{
   cout << "1-----" << endl;
   A peach = 100;
   cout << "peach的地址:" <<&peach<< endl;
   cout << "-----------" << endl;
   cout << "2-----" << endl;
   A peach1(100);
   cout << "peach1的地址:" <<&peach1<< endl;
   cout << "-----------" << endl;
   cout << "3-----" << endl;
   A peach2(peach1);
   cout << "peach2的地址:" <<&peach2<< endl;
   cout << "-----------" << endl;
   return 0;
}
/*
1-----
类A的有参构造被调用
peach的地址:0x7fff9db66340
-----------
2-----
类A的有参构造被调用
peach1的地址:0x7fff9db66348
-----------
3-----
类A的拷贝构造函数被调用
this:0x7fff9db66350
peach2的地址:0x7fff9db66350
-----------
类A的析构函数被调用
类A的析构函数被调用
类A的析构函数被调用
*/

A peach = 100;创建一个名叫peach的A类型的房子,直接让100住进去,不会产生临时对象

A peach1(100);同上

A peach2(peach1);将peach1拷贝一份赋值给peach2

函数传对象

1、按值传递

按值传递时,首先将需要传给函数的参数,调用拷贝构造函数创建一个副本,所有在函数里的操作都是针对这个副本,也正是因为这个原因,在函数体力对该副本进行任何操作都不会影响原参数。

void func1(A temp){
   temp.a = 999;
   cout << "func1函数中tmpe对象a的值" <<temp.a<< endl;
   cout << "func1函数中tmpe对象的地址" <<&temp<< endl;
}
int main()
{
   A apple(20,30);
   func1(apple);
   cout << "-------" << endl;
   cout << "apple对象a的值" <<apple.a<< endl;
   cout << "apple:" <<&apple<< endl;
   return 0;
}
​

/* 类A的有参构造被调用 类A的拷贝构造函数被调用 this:0x7ffe65b8bc30 func1函数中tmpe对象a的值999 func1函数中tmpe对象的地址0x7ffe65b8bc30

类A的析构函数被调用

apple对象a的值20 apple:0x7ffe65b8bc28 类A的析构函数被调用 */

拷贝构造一个apple的副本,传给temp;副本和temp地址为0x7ffe65b8bc30

apple地址为0x7ffe65b8bc28

有临时对象产生,析构函数被调用2次

2、按引用传递(改进)

引用则不然,它是对象本身,只是一个别名而已

void func1(A& temp){
   temp.a = 999;
   cout << "func1函数中tmpe对象a的值" <<temp.a<< endl;
   cout << "func1函数中tmpe对象的地址" <<&temp<< endl;
}
int main()
{
   A apple(20,30);
   func1(apple);
   cout << "-------" << endl;
   cout << "apple对象a的值" <<apple.a<< endl;
   cout << "apple:" <<&apple<< endl;
   return 0;
}
/*
类A的有参构造被调用
func1函数中tmpe对象a的值999
func1函数中tmpe对象的地址0x7ffc02af5c00
-------
apple对象a的值999
apple:0x7ffc02af5c00
类A的析构函数被调用
*/

给apple起个别名叫temp,temp和apple的地址都是0x7ffc02af5c00。

apple地址为0x7ffe65b8bc28

无临时对象产生,析构函数被调用1次

3、按地址传递(改进)

void func1(A* temp){
   (*temp).a = 999;
   cout << "func1函数中tmpe对象a的值" <<(*temp).a<< endl;
   cout << "func1函数中tmpe对象的地址" <<temp<< endl;
}
int main()
{
   A apple(20,30);
   func1(&apple);
   cout << "-------" << endl;
   cout << "apple对象a的值" <<apple.a<< endl;
   cout << "apple:" <<&apple<< endl;
   return 0;
}
/*
类A的有参构造被调用
func1函数中tmpe对象a的值999
func1函数中tmpe对象的地址0x7ffc8b8a1180
-------
apple对象a的值999
apple:0x7ffc8b8a1180
类A的析构函数被调用
*/

直接传地址进去,肯定无新对象产生,但不如引用方便

无临时对象产生,析构函数被调用1次

函数返回对象

A func1(const A& temp){
   cout << "func1函数中temp对象的地址" <<&temp<< endl;
   A peach = {temp.a+100,temp.b+100};
   cout << "func1函数中peach对象的地址" <<&peach<< endl;
   return peach;
}
int main()
{
   A apple(20,30);
   cout << "main函数中apple对象的地址" <<&apple<< endl;
   A t1;
   cout << "main函数中t1对象的地址" <<&t1<< endl;
   t1 = func1(apple);
   cout << "main函数中t1对象的地址" <<&t1<< endl;
   return 0;
}

/* 类A的有参构造被调用 main函数中apple对象的地址0x7ffd86f6db60 类A的有参构造被调用 main函数中t1对象的地址0x7ffd86f6db68 func1函数中temp对象的地址0x7ffd86f6db60 类A的有参构造被调用 func1函数中peach对象的地址0x7ffd86f6db70 类A的拷贝赋值运算符 this:0x7ffd86f6db68 类A的析构函数被调用 main函数中t1对象的地址0x7ffd86f6db68 类A的析构函数被调用 类A的析构函数被调用 */

改进

A func1(const A& temp){
   cout << "func1函数中temp对象的地址" <<&temp<< endl;
   A peach = {temp.a+100,temp.b+100};
   cout << "func1函数中peach对象的地址" <<&peach<< endl;
   return peach;
}
int main()
{
   A apple(20,30);
   cout << "main函数中apple对象的地址" <<&apple<< endl;
   A t1 = func1(apple);
   cout << "main函数中t1对象的地址" <<&t1<< endl;
   return 0;
}
​

/* 类A的有参构造被调用 main函数中apple对象的地址0x7ffcc1692458 func1函数中temp对象的地址0x7ffcc1692458 类A的有参构造被调用 func1函数中peach对象的地址0x7ffcc1692460 main函数中t1对象的地址0x7ffcc1692460 类A的析构函数被调用 类A的析构函数被调用 */

错误

A & func1(const A& temp){
   cout << "func1函数中temp对象的地址" <<&temp<< endl;
   A peach = {temp.a+100,temp.b+100};
   cout << "func1函数中peach对象的地址" <<&peach<< endl;
   return peach;
}
int main()
{
   A apple(20,30);
   cout << "main函数中apple对象的地址" <<&apple<< endl;
   A t1 = func1(apple);
   cout << "main函数中t1对象的地址" <<&t1<< endl;
   return 0;
}

注意,如果返回对象是引用的话,在函数里创建的局部对象,在返回时就被销毁了,这时若再引用该对象就会产生未知错误。