什么情况下可能出现临时对象?

 1、传值参数(pass-by-value parameter)

代码:
  
  
void func(String str) { cout << str; }

当进行这样一个调用时:

代码:
  
  
String s; func(s);

s将被拷贝构造一个备份(临时对象),这个备份参与函数体内的运算,原件不会被改动。

2、返回值(return value)

代码:
  
  
String func() { String s("Hello, world"); return s; }

当s被返回时,会被拷贝构造一个备份(临时对象),这个变量被返回,函数体内的局部变量被销毁。

3、隐式类型转换(Implicitly Typecast)

代码:
  
  
class String { public: String(); String(const char *str); operator=(const String &rhs); };

当实行一个赋值动作:

代码:
  
  
String s; s = "Hello, world";

时,因为String:perator=并没有针对char *和const char *的重载,而最可能的做法就是将const char *隐式转换为一个String对象。在这个过程中就会产生一个String类的临时对象。

4、++ 和 --

代码:
  
  
std::list<int>::iterator iter; iter++;

因为iter++的返回值需要返回原值,所以这个值在增1之前必须保留,这个保留就必须使用一个临时对象。这相当于是返回值的一个典型情况。

5、值存储的STL容器
主要针对std::vector,因为vector具有自动扩充容量的功能,在每次扩充容量的时候,大部分实现的算法是new一个新的内存块,将原有的每个元素拷贝复制到新的内存,然后删除原有的内存块。这期间要使用大量的临时对象。

二、临时对象的弊端
临时对象实际上就是通过拷贝构造函数产生的,没有变量名的const对象,所以每产生这样一个对象,都会调用一次拷贝构造函数。如果这个类很“大”,那么会占用相当的资源和时间,从而降低效率。特别是在第5种情形中,会产生大量的临时对象,情况尤其严重。

三、临时对象的应对之道
解决临时对象的主要方法就是使用指针和引用,减少构造函数调用的机会。

1、避免使用传值参数
实际上绝大部分的传值参数都可以使用常量引用(const &)来代替,而常量引用的参数不会产生临时对象。

代码:
  
  
void func(const String& str) { cout << str; }

这样的写法可以有效的避免在参数传递过程中产生临时对象。

但是,引用通常是由指针实现的,对于基本类型来说,指针操作比直接操作要慢一些,并且增加了操作的复杂性。而基本类型的操作都不复杂,而且很快,所以,对于基本类型来说,没有必要使用常量引用手法。

2、隐式类型转换
隐式类型转换的问题是定义了针对某个类型的构造函数,但是没有定义针对该类型的operator=所导致的,所以,构造函数和operator=必须成对出现。

3、++ 和 --
这个问题已经讨论过很多了,

代码:
  
  
operator++() 的实现方法是 { return ++obj.value; } operator++(int) 的实现方法是 { Class tmp = obj; ++obj.value; return tmp; }

因此,在可能的情况下,使用++i代替i++,就可以避免出现临时对象。

4、在STL容器(尤其是vector)中使用对象指针而不是对象实体作为元素
vector因为有重新分配内存的动作,在这个动作中会产生大量的临时对象,极大的降低效率。解决方法就是使用对象指针作为vector的元素,避免采用对象实体。另外,用reserve事先分配内存也是避免内存交换的方法。

代码:
  
  
std::vector<String *> str_vector;

但是要注意,STL使用的是值(value)语意,所以对容器中指针的管理要自己负责,比如容器析构和copy时,可能要自己多做一些工作,以保证数据的正确性。

更好的方法当然是对指针语意的vector做一个封装。

5、函数的返回值
函数的返回值不能以引用或指针的方式返回函数内部的局部变量,如果需要返回局部变量的值,必须以Object形式返回。在这种情况下,临时对象是不可避免的。这个问题上,《Effective C++》Item 23 给出了更详细的讨论。
对于无法避免的临时对象,我们至少可以将“危害”降到最低点。

利用编译器优化
a) 上面提到,函数必须以值的形式返回局部变量。由于这种形式比较普遍并且无法避免,现代的编译器大部分都可以实行一个叫做 Named Return Value(NRV)的优化。这个优化可以避免不必要的临时对象的产生。在编译器实施NRV优化时,如下的代码:

代码:
  
  
X bar() { X xx; //... operate xx return xx; }

将被优化为类似这样:

代码:
  
  
void bar( X& __result) { __result.X::X(); //... operate __result return; }

很明显的,这个优化减少了一个临时对象的产生。

NRV优化由编译器自动实施,并没有什么编译选项,也无法人工干预。所以,不能依赖于该优化,不过该优化在某些情况下的确可以提高效率。

对NRV的讨论,可以参考《深度探索C++对象模型》一书的“在编译器层面做优化”一章。

b) 目前几乎所有的编译器都可以保证

代码:
  
  
Class c = a + b;

这样的形式将不会产生临时对象。

6、其它
尽可能少的调用
这个问题最典型的现象就是循环的终止条件。

代码:
  
  
for(std::vector<String *>::iterator iter = str_vector.begin(); iter != str_vector.end(); ++iter) { //... }

在这里,每循环一次,iter != str_vector.end() 这个判断就要进行一次,因为str_vector.end()是一个函数调用,因为它的返回值是一个iterator的Object,所以每次调用都会产生一个临时对象。如果写成这样,临时对象就会只被生成一次:

代码:
  
  
std::vector<String *>::iterator end_iter = str_vector.end(); for(std::vector<String *>::iterator iter = str_vector.begin(); iter != end_iter; ++iter) { //... }

四、其它问题
临时对象的生命周期
临时对象是不可见的,但是,在特定的情况下,它的生存周期问题可能导致问题。对这个问题,标准规格上的定义是:

临时对象的被摧毁,应该是对完整表达式求值过程中的最后一个步骤。该完整表达式造成临时对象的产生

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值