总结:
绝不要返回一个local栈对象的指针或引用;绝不要返回一个被分配的堆对象的引用;绝不要返回一个局部对象有可能同时需要多个这样的对象的指针或引用。
条款4中给出了“在单线程环境中合理返回局部静态对象的引用”
提出问题
一旦程序员抓住对象传值的效率隐忧,很多人就会一心一意根除传值的罪恶。他们不屈不挠地追求传引用的纯度,但他们全都犯了一个致命的错误:传递不存在的对象的引用。考虑一个用以表现有理数的类,包含一个函数计算两个有理数的乘积:
class Rational { public: Rational(int numerator = 0, int denominator = 1); ... private: int n, d; // 分子与分母 friend const Rational operator*(const Rational& lhs, const Rational& rhs); };
这个版本operator* 以传值方式返回它的结果,需要付出对象的构造和析构成本。如果你能用返回一个引用来代替,就不需付出代价。但是,请记住一个引用仅仅是一个别名,一个实际存在的对象的名字。无论何时只要你看到一个引用的声明,应该立刻问自己它是什么东西的别名,因为它必定是某物的别名。以上述operator*为例,如果函数返回一个引用,它必然返回某个既有的而且包含两个对象相乘产物的Rational对象引用。
当然没有什么理由期望这样一个对象在调用operator*之前就存在。也就是说,如果你有
Rational a(1, 2); // a = 1/2 Rational b(3, 5); // b = 3/5 Rational c = a * b; // c should be 3/10
期望原本就存在一个值为3/10的有理数对象并不合理。如果operator*返回一个reference指向如此数值,它必须自己创建那个Rational对象。
函数创建新对象仅有三种方法:在栈或在堆上或者在static的静态区域。
在栈空间上创建对象
如果定义一个local变量,就是在栈空间创建对象:
const Rational& operator*(constRational& lhs, const Rational& rhs) { Rational result(lhs.n * rhs.n, lhs.d * rhs.d); return result; } //糟糕的代码!
本来目的是避免调用构造函数,而result却必须像任何对象一样由构造函数构造而来。这个函数返回一个指向result的引用,但是result是一个局部对象,在函数退出时被销毁了。因此这个operator*的版本不会返回指向一个Rational的引用,它返回指向一个“从前的”Rational,一个旧时的Rational,一个曾经被当做Rational但如今已经成空、发臭、败坏的残骸,因为它已经被销毁。任何调用者甚至只是对此函数的返回值做任何一点点运用,就立刻进入了未定义行为的领地。这是事实,任何返回一个指向局部变量引用(或指针)的函数都是错误的。
在堆空间上创建对象