19了解临时对象的来源:
1、首先,确认什么是临时对象。在swap方法中,建立一个对象temp,程序员往往把temp称为临时对象。实际上,temp是个局部对象。C++中所谓的临时对象是不可见的,产生一个non-heap对象,并且没有命名,便是一个临时对象。
2、临时对象的来源有:隐式类型转换,方法返回值,抛出异常的时候。
3、隐式类型转换:在初始化或者赋值的过程中,如果类型不匹配,需要一个适配的临时对象,编译器会构造这个临时对象,使调用成功。
注意:这种适配,只能执行一次,不能执行多次。为什么?假如可以执行多次,从一个对象到另一个对象,存在很多转换路径,编译器不可能找出所有的路径。就算可以找出所有的路径,存在多条转换路径,到底走哪一条呢?
4、隐式类型转换产生的临时对象不可修改,也就是const。不能赋值给non-const引用。为什么?
假设可以赋值给non-const引用,程序员修改这个引用,企图修改原数据,但实际上修改的是临时对象,这与程序员的期望不一致。因此,规定临时对象是const。
5、方法返回值是个临时对象,它没有名称,就是一个返回值。思考,为什么需要这个临时对象?
方法内的局部对象在调用完成后,被销毁。因此,从这个局部对象到调用端,必须有一个临时对象,才能传递过去。
6、方法返回值往往是const对象,这个为了,禁止客户对方法返回值进行赋值。
7、抛出异常的时候,从抛出端到catch语句,必须有一个临时对象才能传递过去,需要注意的是,这个临时对象可以修改。
8、临时对象造成效率低,因为必须承担临时对象的构造和析构成本。因此,应该避免产生临时对象。
对于隐式类型转换:两种方法,使隐式转换不再发生,或者隐式转换不再需要。
对于方法返回值:使用一个技巧,返回值优化RVO(Return Value Optimization),让编译器进行优化。
对于抛出异常的时候:从抛出异常到catch语句,这个临时对象必不可少,当然编译也可能进行优化。需要注意的是,catch语句中应该使用by reference,避免再一次的copy构造,同时避免对象切割。
namespace my
{
class string
{
public:
string(const char* p = 0)
{
try
{
if (p == 0)
{
_p = 0;
return;
}
size_t len = strlen(p) + 1;
_p = new char[len];
strcpy_s(_p, len, p);
cout << "构造" << endl;
}
catch (...)
{
}
}
string(const string& rhs)
{
try
{
if (rhs._p == 0)
{
_p = 0;
return;
}
size_t len = strlen(rhs._p) + 1;
_p = new char[len];
strcpy_s(_p, len, rhs._p);
cout << "拷贝构造" << endl;
}
catch (...)
{
}
}
~string()noexcept
{
try
{
if (_p)
{
delete[] _p;
_p = 0;
}
cout << "析构" << endl;
}
catch (...)
{
}
}
private:
char* _p;
};
}
void func(const my::string& rhs) //参数是string类型
{
}
const my::string func2()
{
return "hello,world";
//返回值是string类型,但使用了char*来进行了隐式构造了一个临时对象
}
const my::string func3()
{
return my::string("hello,world"); //返回值是string类型一个临时对象
}
int main()
{
_CrtSetDbgFlag(33);
func("hello,world"); //实际传入的参数是char*类型,但是由于string类构造函数有char*类型,
所以会发生隐式转换,生成一个临时对象
func2();
func3();
return 0;
}
Comments NOTHING