纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

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

呋喃吖   2021-09-13 我要评论
想了解C++中临时对象的常见产生情况及其解决的方案的相关内容吗呋喃吖在本文为您仔细讲解C++ 临时对象的相关知识和一些Code实例欢迎阅读和指正我们先划重点: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);//直接返回临时对象可以减少
}

在这里插入图片描述

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


相关文章

猜您喜欢

  • node http模块 node中http模块的使用及执行流程

    想了解node中http模块的使用及执行流程的相关内容吗老张在线敲代码在本文为您仔细讲解node http模块的相关知识和一些Code实例欢迎阅读和指正我们先划重点:node,http模块,node,http模块使用下面大家一起来学习吧..
  • C++ pair的用法 C++ pair的用法案例详解

    想了解C++ pair的用法案例详解的相关内容吗物随心转在本文为您仔细讲解C++ pair的用法的相关知识和一些Code实例欢迎阅读和指正我们先划重点:C++,pair,C++,pair用法下面大家一起来学习吧..

网友评论

Copyright 2020 www.sopisoft.net 【绿软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式