我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++中临时对象的常见产生情况及其解决的方案

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++中临时对象的常见产生情况及其解决的方案

前言

在C++中很容易就写出一些代码,这些代码的特点就是偷偷的给你产生了一些临时对象,导致临时对象会调用拷贝构造函数,赋值运算符,析构函数,假如该对象还有继承的话,也会调用父类的拷贝构造函数,赋值运算赋函数等。这些临时对象所调用的函数,都是不必要的开销,也就是说,我本意不想你给我调用这些函数的,但你编译器却给我偷偷的调用了,就是由于我程序员写代码产生临时对象而产生的。

所以临时对象产生的话题也应运而生,这篇文章主要是探讨常见的临时对象产生的情况,及其如何避免和解决这种临时对象产生的方式。

1. 以值传递的方式给函数传参

这种是最常见的产生岭师对象的方式了。

以值传递的方式给函数传参这种方式会直接调用对象的拷贝构造函数,生成一个临时对象传参给函数。当临时对象销毁时候,也是函数形参销毁,也是函数执行完后,就会调用该临时对象的析构函数。此时,无论是调用拷贝构造函数和析构函数,都是额外的开销。

(验证是否调用拷贝构造函数和析构函数,可以在书写拷贝构造函数和析构函数验证)
(验证是否为临时对象可以通过再函数内部修改形参的值,在函数外部打印看看是否修改成功)

验证临时对象的而外开销(1)


# include<iostream>
using namespace std;

class Person{
public:
	Preson()
	{	
		cout << "无参构造函数!" << endl;
	}	
	Person(int a)
	{ 
		m_age = a;
		cout << "有参构造函数!" << endl;
	}
	Person(const Person &p)
	{ 
		m_age = p.m_age;
		cout << "拷贝构造函数!" << endl;
	}
	~Person()
	{
		cout << "析构函数!" << endl;
	}
	int fun(Person p) //普通的成员函数,注意参数是以值的方式调用的
	{
		p.m_age = 20; //这里修改对外界没有印象
		return p.m_age;
	}
	int m_age;	
};

int main()
{
	Person p(10);//初始化
	p.fun(p);
	return 0;
}

先来预测一下调用函数的次数:也就是我们本意想调用的方式:
会执行一次 Person的有参构造函数;
会执行一次Person的析构函数;

于此同时我们看看,编译结果实际情况:

在这里插入图片描述

和我们预期并不一样!!! 多了一次拷贝构造函数和一次析构函数。这两个函数并不是我们希望要得,或者说,这个多余函数开销是不必要的;

产生的原因也很好理解:

由于 fun成员函数里面的形参是Person p,这样会导致在调用这个fun函数时候,会传递过去的是实参的复制品,临时对象,并不是外面main函数的实参,这里可以在fun函数里修改一样形参就可以发现,外面的实参没发生改变。

所以产生的临时对象给形参传参时候,在我们看来类似 Person p = p;实际上是Person p = temp;而这句 Person p = temp;就会发生拷贝构造函数啦,于此同时 fun函数调用结束后,p的声明周期也就结束,所以还会多调用析构函数。

解决方案

如何避免这种临时对象的产生呢?

只要把值传递的方式修改为引用传递的方式即可。这样既不会调用拷贝构造函数,也不会调用多一次临时对象的析构函数。减少额外不必要的开销。

所以我们在函数形参设计时候,能够用引用就用引用的方式,因为这样可以减少对象的复制操作,减少而外的开销。

代码不验证啦,因为比较简单,可以自行验证,修改 fun函数里形参为 Person& p;即可。

2. 类型转换成临时对象 / 隐式类型转换保证函数调用成功

这种方式就是并且把类型转化前的对象当作了形参传递给构造函数,生成临时对象临时对象结束后就会调用析构函数。

验证临时对象的而外开销(2)

代码依旧是上一个代码,只是在main函数做了不一样的动作


# include<iostream>
using namespace std;

class Person{
public:
	Preson()
	{	
		cout << "无参构造函数!" << endl;
	}	
	Person(int a)
	{ 
		m_age = a;
		cout << "有参构造函数!" << endl;
	}
	Person(const Person &p)
	{ 
		m_age = p.m_age;
		cout << "拷贝构造函数!" << endl;
	}
	~Person()
	{
		cout << "析构函数!" << endl;
	}
	int fun(Person p) //普通的成员函数,注意参数是以值的方式调用的
	{
		p.m_age = 20; //这里修改对外界没有印象
		return p.m_age;
	}
	int m_age;	
};

int main()
{
	Person p;
	p = 1000; 
	return 0;
}

首先预测一下该代码执行的结果:

首先 调用一次无参构造函数,一次析构函数。

其次看看编译器运行的结果:

在这里插入图片描述

为啥会多出一个有参构造函数呢和析构函数呢?

其实是由于 p = 1000;这句引起的,这里p的类型为 Person,而 1000为 int 类型,很明显类型不一致。
编译器其实偷偷的进行了类型转换,如何转换呢?看编译器的调用都可以发现,其实就是创建一个临时对象,这个临时对象调用了有参构造函数,并且把 这个1000作为形参,传入有参构造函数,当这个函数调用结束后,对象也就销毁了,所以临时对象会调用析构函数。

解决方案

其实很简单的:
只要把单参数构造函数的复制(复制)语句,改为初始化语句就行。
那什么是复制语句和初始化语句呢?
两者的区别就是
一个是创建对象同时赋值对象,也就是说创建时候就马上初始化,这就是初始化;
一个是创建对象时候不赋值对象,而是等对象创建好,过后使用再赋值对象,这就是赋值语句啦;

那么我们只需要把:


	Person p;
	p = 1000; 
	修改为:
	Person p = 1000;

这样就不会有多一次的有参构造和析构的开销了。

3. 函数返回对象时候

在函数返回对象时候,会创建一个临时对象接收这个对象;从而调用了拷贝构造函数,和析构函数。
当你调用函数,没有接收返回值时候,就会调用析构函数,因为都没有人接收返回值了,自然而然析构了。当你调用时候,有接收返回值时候,这个时候,并不会多调用一次析构函数,而是直接把临时对象返回值,给了接受返回值的变量来接收。

验证临时对象的而外开销(3)

代码:


# include<iostream>
using namespace std;

class Person{
public:
	Preson()
	{	
		cout << "无参构造函数!" << endl;
	}	
	Person(int a)
	{ 
		m_age = a;
		cout << "有参构造函数!" << endl;
	}
	Person(const Person &p)
	{ 
		m_age = p.m_age;
		cout << "拷贝构造函数!" << endl;
	}
	~Person()
	{
		cout << "析构函数!" << endl;
	}
	int fun(Person p) //普通的成员函数,注意参数是以值的方式调用的
	{
		p.m_age = 20; //这里修改对外界没有印象
		return p.m_age;
	}
	int m_age;	
};
Person test(Person & p)
{
	Person p1; //这里会调用无参构造函数和结束的一次析构函数
	p1.m_age = p.m_age;
	return p1; //这里会多调用一次临时拷贝和析构函数
}
int main()
{
	Person p;
	test(p);
	return 0;
}

看看执行结果:

在这里插入图片描述

其实很好理解:就是以值的方式返回时候,就会多调用一次拷贝构造和析构函数;
结果中的第一个析构时test函数里p1对象的析构,第二个析构时 返回值时候临时对象的析构;第三个析构时main函数里p对象的析构;

请注意我的test函数在调用时候,我并没有给返回值,此时;当我以返回只接受时候,就会有不一样结果:不一样的地方就是,少了一次析构函数,其实少的这次析构函数时test函数里返回值产生的临时对象,因为,当你有对象接收返回值时候,就会直接把test函数里返回值临时对象给初始化接收返回值对象;

即,我修改main函数的代码:


int main()
{
	Person p;
	Person p2 = test(p); //此时test返回值临时对象并不会析构,
						//因为这里把临时对象直接初始化了p2;
	return 0;
}

在这里插入图片描述

可以说时编译器优化手段吧。本来说 p2对象因该也是需要调用多一次拷贝构造函数的,但是由于有临时对象的初始化,所以p2对象就直接接管临时对象了。所以上面结果最后的析构函数,其实时p2对象的析构,并不是临时对象的析构。

解决方案

其实也很简单的解决办法:有两种:

  • 当我们在接收函数返回的对象时候,可以用右值引用接收,因为该函数返回值是一个临时变量,用一个右值引用接收它,使得它的生命周期得以延续,这样就少调用一次析构函数的开销。(当然普通的对象接收也是可以)
  • 当我们在设计函数里的return 语句中,不是返回创建好的对象,而是返回我们临时创建的对象,即使用retturn 类类型(形参); 这个时候,就可以直接避免 return 对象;返回时候又要调用多一次构造函数。

这两种行为就可以避免了构造函数和析构函数的产生。

但是,右值引用我还没有写到这文章,所以先不讲右值引用的方案,讲第二种方案:
也就是设计函数返回语句 return时候,不要直接返回对象,而是返回临时对象,这个临时对象。


把这个代码修改:
Person test(Person & p)
{
	Person p1; //这里会调用无参构造函数和结束的一次析构函数
	p1.m_age = p.m_age;
	return p1; //这里会多调用一次临时拷贝和析构函数
}

修改为:
Person test(Person &p)
{
	return Person(p.m_age);//直接返回临时对象,可以减少
}

在这里插入图片描述

其实,只要以值得形式返回对象都会调用多一次拷贝构造函数,所以我们尽量避免这种情况,用合适的方式解决它。

到此这篇关于C++中临时对象的常见产生情况及其解决的方案的文章就介绍到这了,更多相关C++ 临时对象内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++中临时对象的常见产生情况及其解决的方案

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Golang 编程中常见的精度丢失情况及解决方案

精度丢失在编程中属于一个普遍问题,尤其在使用 Golang 这样的语言时更为突出。Golang 是一种静态类型、编译型语言,其对精度要求非常高,因此开发者需要格外留意精度丢失的问题。本文将探讨在 Golang 编程中常见的精度丢失情况,并提
Golang 编程中常见的精度丢失情况及解决方案
2024-02-23

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录