Q1:讨论一下三种语句产生临时对象的情况
//1.
T c = a + b;
//2.
c = a + b;
//3.
a + b;
- 对第一种情况,实现时不会产生临时对象,可能会直接以拷贝构造的情况,将 a + b 的值放入 c 中,或是直接进行NRV优化,导致直接在对象 c 中求值操作
以下讨论另外两种情况
Q2:对 c = a + b 的情况
• 这种情况下会产生临时对象,具体代码情况如下:
// T temp = a + b;
T temp;
temp.oprator+(a,b); // (1)
//c = temp;
c.operator=(temp); // (2)
temp.T::~T()
• 上述的句子(1),未构造的临时对象被赋值给operator+(),这意味着:表达式的结果被复制构造至临时对象中,或者以临时对象进行NRV
• 直接传递 c 到运算符函数(2)中都是有问题的,因为该对象不是一个新定义的对象,而运算符函数不为其外加参数调用析构函数,所以在调用运算符函数前必须要先调用析构函数
• 因此,该语句将会被转换为如下形式:
c.T::~T();
c.T::T(a + b); //产生临时对象存储 a + b
由于原语句希望调用赋值操作符函数,而转换后的语句调用析构函数与复制构造函数,因此可能转换前后语意不同,是不安全的,而且会产生临时对象
• T c = a + b 总是比 c = a + b 更有效的被编译器转换出来
Q3:对 a + b 的情况
• 会产生临时对象用来存放其值,这种情况下重点关注“临时对象的生命期”
• 考虑以下例子:
Eg:
string s,t;
print("%s\n",s + t);
其中 s + t 将产生一个临时对象,分析该临时对象的生命期
• 下面是对该算是的一个可能的早期标准转换,虽然语法正确,但是很有问题:
Eg:
string temp1 = operator+(s,t);
const char * temp2 = temp1.operator const char*(); //转换操作符
temp1.~string(); //销毁temp1
printf("%s\n",temp2); //此时temp2 指向一个被释放的内存
• C++ 标准规定,临时对象的摧毁应该是对完整表达式求值过程中的最后一个步骤
• 当临时性对象是根据程序的执行期语意,有条件的被产生出来时,临时对象的生命规则就有些复杂
Eg:
if(s + t || u + v);
只有当 s + t 的值为 false 时,才会计算 u + v 的值。因此,必须先知道临时对象是否被产生,才能确定是否应该在表达式结束前销毁临时对象
• 解决方法是:某些形式的条件测试必须被安插进来,以决定是否要摧毁和第二个算式有关的临时对象
• 临时对象的声明规则有两个例外:
1. 当表达式被用来初始化一个对象时,要求持有该表达式执行结果的临时对象,应该存留到对象的初始化操作完成为止。但注意
以下情况:
Eg:
string n,s;
cpmst char * ns = n + s;
//会转化为如下情况:
string temp;
operator+(temp,n,s);
ns = temp.string::operator char *();
//此时对象的初始化操作已完成,才销毁临时对象,但是该销毁操作将会导致 ns 指向一个未定义的堆内存
temp.string::~string();
2. 当一个临时对象被一个引用绑定时,此时临时对象将残留,直到该引用的生命结束,或者临时对象的 scope 结束。如:
Eg:
const string &space = " ";
//会转化为如下情况:
string temp;
temp.string::string(" ");
const string & space = temp;
此时如果销毁临时对象temp,则该引用将没有任何用处