Effective C++笔记: 设计与声明(二)

 

Item 21: 当你必须返回一个对象时,不要试图返回其引用

一个函数创建一个新对象仅有两种方法:在栈上或者在堆上。

 

在栈上分配:

栈上的生成物通过定义一个局部变量而生成。使用这个策略,你可以用这种方法试写 operator*

const Rational& operator*(const Rational& lhs,   // warning! bad code!
                          const Rational& rhs)
{
  Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
  return result;
}

你可以立即否决这种方法,因为你的目标是避免调用构造函数,而 result 正像任何其它对象一样必须被构造。一个更严重的问题是这个函数返回一个引向 result 的引用,但是 result 是一个局部对象,而局部对象在函数退出时被销毁。返回的引用指向一个不存在的object

 

在堆上分配:

那么,让我们考虑一下在堆上构造一个对象并返回引向它的引用的可能性。基于堆的对象通过使用 new 而开始存在,所以你可以像这样写一个基于堆的 operator*

const Rational& operator*(const Rational& lhs,   // warning! more bad
                          const Rational& rhs)   // code!
{
  Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}

哦,你还是必须要付出一个构造函数调用的成本,因为通过 new 分配的内存要通过调用一个适当的构造函数进行初始化,但是现在你有另一个问题:谁是删除你用 new 做出来的对象的合适人选?

即使调用者尽职尽责且一心向善,它们也不太可能是用这样的方案来合理地预防泄漏:

Rational w, x, y, z;

w = x * y * z;                     // same as operator*(operator*(x, y), z)

这里,在同一个语句中有两个 operator* 的调用,因此 new 被使用了两次,这两次都需要使用 delete 来销毁。但是 operator* 的客户没有合理的办法进行那些调用,因为他们没有合理的办法取得隐藏在通过调用 operator* 返回的引用后面的指针。这是一个早已注定的资源泄漏。

 

采用Static

那么,让我们考虑一下在堆上构造一个对象并返回引向它的引用的可能性。基于堆的对象通过使用 new 而开始存在,所以你可以像这样写一个基于堆的 operator*

const Rational& operator*(const Rational& lhs,   // warning! more bad
                          const Rational& rhs)   // code!
{
  Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}

哦,你还是必须要付出一个构造函数调用的成本,因为通过 new 分配的内存要通过调用一个适当的构造函数进行初始化,但是现在你有另一个问题:谁是删除你用 new 做出来的对象的合适人选?

即使调用者尽职尽责且一心向善,它们也不太可能是用这样的方案来合理地预防泄漏:

Rational w, x, y, z;

w = x * y * z;                     // same as operator*(operator*(x, y), z)

这里,在同一个语句中有两个 operator* 的调用,因此 new 被使用了两次,这两次都需要使用 delete 来销毁。但是 operator* 的客户没有合理的办法进行那些调用,因为他们没有合理的办法取得隐藏在通过调用 operator* 返回的引用后面的指针。这是一个早已注定的资源泄漏。

 

使用Static

注意到无论是在栈上的还是在堆上的方法,为了从 operator* 返回的每一个 result,我们都不得不容忍一次构造函数的调用。也许你想起我们最初的目标是避免这样的构造函数调用。也许你认为你知道一种方法能避免除一次以外几乎全部的构造函数调用。也许下面这个实现是你做过的,一个基于 operator* 返回一个引向 static Rational 对象的引用的实现,而这个 static Rational 对象定义在函数内部:

const Rational& operator*(const Rational& lhs,    // warning! yet more
                          const Rational& rhs)    // bad code!
{
  static Rational result;             // static object to which a
                                      // reference will be returned

  result = ... ;                      // multiply lhs by rhs and put the
                                      // product inside result
  return result;
}

就像所有使用了 static 对象的设计一样,这个也会立即引起我们的线程安全(thread-safety)的混乱,但那是它的比较明显的缺点。为了看到它的更深层的缺陷,考虑这个完全合理的客户代码:

bool operator==(const Rational& lhs,            // an operator==
                const Rational& rhs);           // for Rationals

Rational a, b, c, d;

...
if ((a * b) == (c * d))  {
   
do whatever's appropriate when the products are equal;
} else    {
   
do whatever's appropriate when they're not;
}

猜猜会怎么样?不管 abcd 的值是什么,表达式 ((a*b) == (c*d)) 总是等于 true

如果代码重写为功能完全等价的另一种形式,这一启示就很容易被理解了:

if (operator==(operator*(a, b), operator*(c, d)))

注意,当 operator== 被调用时,将同时存在两个起作用的对 operator* 的调用,每一个都将返回引向 operator* 内部的 static Rational 对象的引用。因此,operator== 将被要求比较 operator* 内部的 static Rational 对象的值和 operator* 内部的 static Rational 对象的值。如果它们不是永远相等,那才真的会令人大惊失色了。

 

正确的做法:

写一个必须返回一个新对象的函数的正确方法就是让那个函数返回一个新对象。对于 Rational operator*,这就意味着下面这些代码或在本质上与其相当的某些东西:

inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
  return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

当然,你可能付出了构造和析构 operator* 的返回值的成本,但是从长远看,这只是为正确行为付出的很小的代价。

 

总结:

绝不要返回pointerreference指向一个local stack对象;

绝不要返回reference指向一个heap allocated对象;

绝不要返回pointerreference指向一个local static对象,而该对象又可能同时需要多个。

Item 4 中提供了一份如何在“单线程环境中合理返回reference指向一个local static对象”的实例)

 

Item 22: 将数据成员声明为 private

protected 数据成员不比 public 数据成员更具有封装性,假设我们有一个protected成员变量,而我们最终取消了它,则所有使用它的derived classed都会被破坏!

从封装的观点来看,实际只有两个访问层次:private(提供了封装)与其他(不提供封装)。

 

总结:

声明数据成员为 private。它为客户提供了访问数据的一致性,可细微划分的访问控制,更多的约束条件,而且为类的作者提供了实现上的弹性。

protected 并不比 public 的封装性强。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值