12.2 临时对象
1. 类的临时对象是在很多种上下文中产生的:将一个右值绑定到一个引用上,返回一个右值,创建右值的类型转换,抛出一个异常,进入一个异常处理函数以及某些初始化当中。即使在临时对象的创建被刻意避免的情况下(拷贝对象时),所有的语义约束也必须被当作创建了临时对象一样来看待。【例:即使没有调用拷贝构造函数,所有语义约束比如访问权限等都应该被满足。】
2. 【例
class X {
// ...
public:
// ...
X(int);
X(const X&);
~X();
};
X f(X);
void g()
{
X a(1);
X b = f(X(2));
a = f(a);
}
对于 X b = f(X(2)) 这句,一种可能的编译器实现是:先在一个临时变量中创建 X(2) ,然后再用 X 的拷贝构造函数将这个临时变量其传给 f() ;另一种可能的实现是:在放置 f() 的参数的地方创建 X(2) 这个对象(不是临时的)。
同时, f(X(2)) 的返回值可能会用一个临时对象来放置,然后将这个临时对象拷贝给 b ;也可能是另一种情况: b 直接盛放 f(X(2)) 的返回值(返回值直接在 b 这个地方构造)。
看另外一个例句, a=f(a) 这个表达式也需要一个临时变量:或者替换参数中的 a ,或者替换返回值,从而避免出现 a 的名字混乱问题。
】
3. 当编译器的实现产生了一个类(该类具有非 trivial 构造函数)的临时对象时,编译器必须保证调用该临时对象的构造函数。析构函数也类似。临时对象一般是在表达式计算的最后一步销毁的(该表达式是个完整表达式 【按:不是任何表达式的子表达式】 ,词法上要包含临时对象的创建点);即使该表达式以抛出异常这种形式来作为结束,也是一样的。
4. 临时对象的销毁时机在两种情况下是例外。其一,表达式作为另外一个对象定义时的初始化值,在这种情况下临时对象一直盛放着该表达式的值,直到这另外一个对象的初始化工作完成,它才销毁。这另外一个对象是通过该对临时对象进行拷贝来初始化的;在拷贝的过程中,编译器的实现可以多次调用拷贝构造函数;临时对象在拷贝结束之后,初始化工作结束前或结束时才销毁。如果在初始化值计算的过程中创建了多个临时对象,临时对象会以与创建完成顺序相反的顺序来销毁。
5. 其二,是将临时对象绑定到一个引用时。分两种情况:①引用所绑定的临时对象;②一个临时对象被一个引用给绑定了,同时这个临时对象还是另一个完整对象的子对象【这个完整对象我们称之为该临时对象的外层完整对象】。 ①中的临时对象和②中的外层完整对象一般来说与引用的生命周期相同,除了以下几种情况:
a) 绑定到临时对象的是一个数据成员,该数据成员被用在构造函数的构造初始化器中,这种临时对象的生命周期在构造函数执行完的时候结束
b) 绑定到临时对象的是一个函数的参数,这种临时对象的生命周期在包含这个函数的完整表达式计算完成之后结束
c) 绑定到临时对象的是一个函数的 return 语句的返回值,这种临时对象的生命周期在函数退出的时候结束
在以上 3 种情况中,为了初始化引用而进行的表达式计算所产生的中间临时对象,除了最后绑定到引用的那个临时对象,全部在完整表达式结束之后销毁,销毁顺序与它们创建完成时的顺序向相反。如果两个或多个被绑定的临时对象的生命周期相同,那么它们以创建完成时的相反顺序销毁。除此之外,被绑定的临时对象的销毁过程应该考虑到自动对象和静态对象这些情况;也就是说:假设 obj1 是一个自动或静态对象,它在临时对象之前就被创建了,那么临时对象必须在 obj1 销毁之前销毁;假设 obj2 是一个自动或静态对象,它在临时对象之后才被创建,那么临时对象必须在 obj2 销毁之后销毁。【例:
class C
{
public:
C();
C(int);
friend C operator+(const C&, const C&);
~C();
};
C obj1;
const C& cr = C(16)+C(23);
C obj2;
表达式 C(16)+C(23) 创建了 3 个临时对象。第一个临时对象 T1 用来盛放 C(16) 这个表达式的值,第二个临时对象 T2 用来盛放 C(23) 这个表达式的值,第三个临时对象 T3 用来盛放这个表达式相加后的结果;然后 T3 就被绑定到了 cr 。 T1 与 T2 谁先创建是没有定义的;假设编译器先创建 T1 ,那它就必须保证 T2 在 T1 之前销毁。 T1 与 T2 被绑定到 operator+ 这个函数的引用类型的参数上。绑定到 cr 上的 T3 在 cr 的生命周期结束的时候被销毁(在本例中,是在程序的结尾)。除此之外, T3 销毁的顺序要考虑到静态存储类型的对象 【按:在本例中,obj1 和obj2 都是全局的,被看作是静态存储类型】 的销毁顺序。也就是说:由于 obj1 在 T3 之前创建, T3 在 obj2 之前创建,所以必须保证 obj2 在 T3 之前销毁, T3 在 obj1 之前销毁。
】