上上周周末翻《effective c++》,条款18看到一段代码:
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
class Month {
public:
static Month Jan() { return Month(1); }
};
Date d(Month::Jan(), Day(30), Year(1995));
看到这里的时候,猛然感觉有问题。
在函数d(Month::Jan(), Day(30), Year(1995));中(也就是Data的构造函数),第一个变量为const Month& m,是一个引用,而函数Jan()返回了一个临时变量(返回值保存在栈中,之后通过拷贝构造临时变量)。也就是说,将一个引用指向了一个临时变量,而且这个临时变量的作用域非常不明确。初看上去,在Data()的第一个参数赋值完成后,临时变量就失效了。
当时在网上找了好久,期望看到其他人对这段代码的异议,可惜没有发现。
周二的时候,问了一下师兄。
师兄说这样是有问题的,估计编译都会出现警告,但是如果Date的构造函数中做了特殊的实现(比如变量拷贝等等),那么这样写就是正确的。我想了想,确实有道理,师兄还提出了一个观点:c++里似乎会延长这样的临时变量的作用域。他建议我去看一下这段代码的汇编实现,就能彻底搞清楚了。
可是我不以为然,想当然的笃定这样的代码肯定会编译失败。
而且觉得,这样的可能存在争议的代码,本来就不应该写出来,如果是我来实现,会在static Month Jan()中增加一个static Month(1)成员,效率高,逻辑清晰。
以至于后来也确实没有去反汇编这段代码,甚至都没有去验证是否存在编译警告问题......
这件事本来就这样过去了,可是今天继续翻《effective c++》的时候,又看到了一段代码:
class Rational {
public:
Rational(int numberator = 0,
int denominator = 1); //允许隐式转换
...
};
const Rational operator* (const Rational& lhs,
const Rational& rhs)
{...}
Rational oneFourth(1, 4);
Rational result;
result = 2 * oneFourth;
明显的,这里一样使用了临时变量,书上明确写了一句话: “万岁,通过编译了”。
看来这样做,编译是可以成功的,而且好像是一种再普通不过的用法。
于是一边哀叹自己的浅薄,一边去查资料搞定这个问题。
终于在c++标准里找到这样的解释:
C++标准对临时对象的生存期(life time)的规定,可见标准12.2节第3-5条。第3条讨论了临时对象的析构时间:
3 .... Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception.(这其中涉及到的full-expression的定义,参见1.9节,一般指对一个变量的初始化)。
但在第4、5条中,分别讨论了两个例外情形,
1 将临时对象作为初始化因子,例如string s = string("hello world");
2 将一个常量引用变量绑定到这个临时对象上。
在这两种情况中可以通过一个名字来存取这个对象,此对象的生存期就将延长到变量名的作用域结束。除此之外,都按照第3条处理。
这样解释之后,一切顺理成章。
(原文时间2013-10-1)
网友评论