第四章~
# 条款 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 可以提高效率。