C++对象模型——临时性对象 (第六章)

6.3    临时性对象 (Temporary Objects)

    如果有一个函数,形式如下:
T operator+(const T &, const T &);
    以及两个T objects,a和b,那么:
a + b;
    可能会导致一个临时性对象,以放置传回的对象.是否会导致一个临时性对象,视编译器的进取性(aggressiveness)以及上述操作发生时的程序上下关系(program context)而定.例如下面这个片段:
T a, b;
T c = a + b;
    编译器会产生一个临时性对象,放置a+b的结果,然后再使用T的copy constructor,把该临时性对象当作c的初值.然而 更好的转换则是直接以拷贝构造的方式,将a+b的值放到c中,于是就不需要临时性对象,以及对其constructor和destructor的调用了.
    此外,视operator+()的定义而定,named return value(NRV)优化(详见2.3节)也可能实施起来.这将导致直接在上述c对象中求表达式结果, 避免执行copy constructor和具名对象(named object)的destructor.
    三种方式所获得的c对象,结果都一样.其间的 差异在于初始化的成本.一个编译器可能给任何保证吗?严格来说没有,C++ Standard允许编译器对于临时性对象的产生有完全的自由度.
    但实际上,几乎任何表达式如果有这种形式:
T c = a + b;
    而 其中的加法运算符被定义为:
T operator+(const T &, const T &);
    或
T T::operator+(const T &);
    那么 实现时根本不产生一个临时性对象.
    然而注意,意义相当的assignment语句:
c = a + b;
    不能够忽略临时性对象.
    所以这样的初始化操作:
T c = a + b;
    总是 比下面的操作更有效率地被编译器转换:
c = a + b;
    第三种运算形式是,没有出现目标对象:
a + b;    // no target
    这时候 有必要产生一个临时对象,以放置运算后的结果.虽然看起来有点怪异,但这种情况实际上在子表达式中十分普遍.例如,如果这样写:
String s("hello"), t("world"), u("!");
    那么不论:
String v;
v = s + t + u;
    或
printf("%s\n", s + t);
    都会产生一个临时对象,与s + t相关联.
    最后一个表达式带来了一个论题,那就是 "临时对象的生命周期".
    一种比较被喜欢的转换方式是在调用printf()之后实施String destructor.在C++ Standard下,这正是该表达式的必须转换方式.标准规格这样将:
     临时性对象的被摧毁,应该是对完整表达式求值过程中的最后一个步骤,该表达式造成临时对象的产生.
     什么是一个完整表达式?非正式地说,它是被涵括的表达式中最外围的那个.下面这个表达式:
// tertiary full expression with 5 sub-expressions
((objA > 1024) && (objB > 1024)) ? objA + objB : foo(objA, objB);
    一种有五个子表达式,内带在一个"?:完成表达式"中.任何一个子表达式所产生的任何一个临时对象,都应该在完整表达式被求值完成后,才可以销毁.
    当临时性对象是根据程序的执行期语意有条件地被产生出来时,临时性对象的生命规则就显得有些复杂了.举个例子,想这样的表达式:
if (s + t || u + v)
    其中的u+v子算式只有在s+t被评估为 false 时,才会开始被评估.与第二个子算式有关的临时性对象必须被销毁.但是,很显然地,不可以被无条件地销毁.也就是说,希望只有在临时性对象被产生出来的情况下才去销毁它.(如果第一个子算式为 true,则不产生第二个临时性对象,不需要销毁)
    把临时性对象的destructor放在每一个子算式的求值过程中,可以免除"努力追踪第二个子算式是否真的需要被评估".然而在C++ Standard的临时对象生命规则中,这样的策略不再被允许.临时性对象在完整表达式尚未评估完全之前,不得被销毁.也就是说,某些形式的条件测试现在必须被插入进来,以决定是否要晓辉何第二算式有关的临时对象.
    临时对象的生命规则有两个例外.第一个例外发生在表达式被用来初始化一个object时.例如:
bool verbose;
...
String progNameVersion = !verbose ? 0 : progName + progVersion;
    其中progName和progVersion都是String objects.这时候会生出一个临时对象,放置加法运算符的运算结果:
String operator+(const String &, const String &);
    临时对象必须根据对verbose的测试结果有条件地解构.在临时对象的生命规则下,它应该在完整的"?:表达式"结束评估后尽快被销毁.然而,如果progNameVersion的初始化需要一个copy constructor:
progNameVersion.String::String(temp);
    那么临时性对象的解构(在"?:完整表达式"之后)当然那就不是期望的. C++ Standard要求:
    . ..凡是含有表达式执行结果的临时性对象,应该存留到object的初始化操作完成为止.
    临时性对象的生命规则的 第二个例外是"当一个临时性对象被一个reference绑定"时,例如:
const String &space = " ";
    产生出这样的程序代码:
// C++ pseudo Code
String temp;
temp.String::String(" ");
const String &space = temp;
    很明显,如果临时性对象现在被销毁,那个reference也就没有用了.所以 C++ Standard要求:
    如果一个临时性对象被绑定在一个reference,对象将残留,直到被初始化的reference的生命结束,或直到临时对象的生命范畴(scope)结束--视哪一种情况先到达而定
.

临时性对象的迷思

    有一种说法是,由于当前的C++编译器会产生临时性对象,导致程序的执行比较没有效率.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值