第四章~

# 条款 18:让接口容易被正确使用,不易被误用

这点无论在什么语言中都是很重要的规则。

总之要尽可能避免可能的错误,阻止误用,可以通过建立新类型、限制类型,限制传入值等方法。

然后可以通过 shared_ptr 去管理新建的对象,从而防止内存泄漏的风险,同时可以防范 DLL 问题,可以被用来自动解除互斥锁等。

# 条款 19:设计 class 犹如 type

这一条款其实非常宏观,核心思想就是对于设计 class 要十分谨慎,思考的角度要尽可能全面。作者给出的方式就是思考一系列问题(比较难概括,最好看原文):

  • 新 type 的对象应该如何被创建和销毁?
  • 对象的初始化和对象的赋值该有什么样的差别?
  • 新 type 的对象如果被 passed by value,意味着什么?
  • 什么是新 type 的 “合法值”?
  • 你的新 type 需要配合某个继承图系吗?
  • 你的新 type 需要什么样的转换?
  • 什么样的操作符和函数对此新 type 而言是合理的?
  • 什么样的标准函数应被驳回?
  • 谁改取用新 type 的成员?
  • 什么是新 type 的 “未声明接口”?
  • 你的新 type 有多么一般化?
  • 你真的需要一个新 type 吗?

# 条款 20:宁以 pass-by-reference-to-const 替换 pass_by_value

# 条款 21:必须返回对象时,别妄想返回其 reference

这两点其实从现在的眼光来看有些平常了,学 c++ 的引用的时候大概率都会了解到。总的来说,传值时对非基础类型来说尽量传递 const 引用,因为直接传值会默认使用 copy 传值的方式传递,会有额外开销。然后不要返回指向 local stack 对象的指针或引用,这点也是老生常谈了。

# 条款 22:将成员变量声明为 private

这点就是要将变量都尽可能封装起来,读写等通过函数接口的方式开放,从而方便开放特定权限和减少变更的成本,为 class 作者提供充分的实现弹性。这里有点要注意下,就是 protected 这个关键词的封装性其实并不比 public 好,因为取消一个 public 的接口会破坏所有调用接口的类,而取消一个 protect 接口则会破坏所有派生类,这两种的破坏性很多时候都是很致命的。

# 条款 23: 宁以 non-member、non-friend 替换 member 函数

这点乍一看比较反直觉,但其实本质还是尽可能提高封装性。这里需要注意,friend non-member 的函数和 member 函数封装性是等同的,都可以直接访问 private 变量,一定是 non-member non-friend 函数才能获得更好的封装性,通过调用 class 提供的 public 成员去实现一些工具函数的时候会很有用。

然后为了让这个 non-member non-friend 的函数显得更自然,作者提供了一种方法,就是将这个函数和对应 class 放在同一命名空间中:

namespace WebBrowserStuff{
  class WebBrowser{ ... };
  void clearBrowser(WebBrowser& wb);
}

# 条款 24:若所有参数皆需类型转换,请为此采用 non-member 函数

这点看描述感觉云里雾里的,其实本质也确实很难描述,也许需要通过例子。

书中举了一个有理数的 class 的例子:

class Rational{
public:
    Rational(int numerator=0,int denominator=1);// 构造函数刻意不为 explicit
																								// 允许 int-to-Rational
    int numerator()const;
    int denominator()const;
private:
    int numerator;
    int denominator;
}

假如使用 member 函数:

class Rational{
public:
	...
	const Rational operator*(const Rational& rhs)const;
};

可以解决有理数的相乘:

Rational oneEight(1,8);
Rational oneHalf(1,2);
Rational result=oneHalf*oneEight;
result=result*oneEight;

但当混入整形时,却会发生错误:

result=oneHalf*2;// 很好
result=2*oneHalf;// 错误

因为上面两句代码可以翻译成:

result=oneHalf.operator*(2);// 很好
result=2.operator*(oneHalf);// 错误

因为 int 类型没有重载 operator*(const Rational& rhs),因此报错了。

所以需要实现一个 non-member 函数:

const Rational operator*(const Rational& lhs,const Rational& rhs){
	return ..
}

通过这个函数上面两种混合整形的表达式都可以编译通过了,因为会隐式调用 Rational 的构造函数,相当于:

result=operator*(Rational(2), oneHalf);

# 条款 25:考虑写出一个不抛异常的 swap 函数

这点主要是为了处理 std 的默认的 swap 效率不够高的情况。主要是针对 “pimpl 手法” 的类,即 “以指针指向一个对象,内含真正数据”,这种类的 swap 只需要改变指针指向对象,不需要复制实际数据,因此自己实现特化的 swap 可以提高效率。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Nirvana 支付宝

支付宝