f作者:LogM
本文原载于 https://segmentfault.com/u/logm/articles,不允许转载~
4. 设计与声明
-
4.1 条款18:让接口不易被误用
- 简单来说,就是考虑用户可能的误用行为,在代码中规避。比如工厂函数返回值为"智能指针"类型,避免用户使用一般指针管理资源带来的资源泄漏风险;比如将 operator* 的返回类型设为const,避免用户写出"a*b=c"这样的代码。
-
4.2 条款19:像设计type一样设计class
- 作者的意思是,设计class要考虑很多细节,尽量使设计出来的class像C++内置类型一样有极大的可用性和鲁棒性。
-
4.3 条款20:宁以 pass-by-reference-to-const 替换 pass-by-value
- 原因:a. 参数使用引用传递,不需要构造新对象,比较快;b. 避免对象切割问题。
-
对象切割(slicing):当派生类对象以 by-value 方式传递参数并被视为基类对象,基类的拷贝构造函数在构造时会把派生类特有的性质抹除。
-
"引用"在编译器底层的实现就是指针(指针的本质是int类型的变量),所以在以下场景,"引用"并不一定比pass-by-value快:a. C++内置类型;b. STL的迭代器(底层实现是指针);c. 函数对象(底层实现是指针)。
-
4.4 条款21:不要让函数返回值是指向局部变量的引用或指针
- 原因应该很容易理解:局部变量在函数调用结束后就销毁了,那么这个函数返回的引用和指针指向的内存已经无效了。
-
4.5 条款22:将成员变量声明为 private
- 一致性:成员变量为 private,则用户想访问成员变量必须通过成员函数,所以用户就不用试着记住是不是要加括号,因为成员函数都要加括号。
- 安全性:通过成员函数控制用户对成员变量的读写权限。
- 封装性:class的版本发生变化,但提供给用户的API还是可以保持不变。
-
4.6 条款23:宁以 non-menber、non-friend 替换 member 函数
- 使用场景如下代码所示。作者倾向于non-member、non-friend的理由是:它们无法访问private的成员变量,在封装性上更好,编译时候的依赖程度也低。
-
作者的说法有一定道理,但我不完全同意作者的观点:
- a. member函数可以访问private的成员变量,并不意味着用户就可以接触到private的成员变量,你在写代码的时候不让这个member函数访问private的成员变量不就可以了?(此时问题变成了:如何确保写代码的人不在这个函数中滥用private的成员变量)
- b. 有些情况,使用non-member、non-friend函数会降低代码接口的一致性。作者的解决思路是把non-member、non-friend函数放在和类同一个namespace下,我想了想,这么做一致性还是不如直接写member函数。
-
class WebBrowser { public: ... void clearCache(); void clearHistory(); void clearCookies(); ... } //假如现在要写一个函数clearEverything(),作用是同时清理cache、history、cookies。 //使用member函数的情况 class WebBrowser { public: ... void clearEverything(); ... } void WebBrowser::clearEverything() { clearCache(); clearHistory(); clearCookies(); } //使用non-member函数的情况 void clearBrower(WebBrowser& wb) { wb.clearCache(); wb.clearHistory(); wb.clearCookies(); }
-
4.7 条款24:若所有参数都需要类型转换,请把这个函数写成 non-member 函数
-
//第一种情况:乘法函数为member函数 class Rational { public: ... Rational(int numerator, int denominator); Rational(int num); //这个构造函数使得该类支持从int到Rational的类型转换。如果前面加explict则说明不支持隐式类型转换仅支持显式转换,现在没加,支持隐式转换 const Rational operator* (const Rational& rhs) const; } //使用 Rational lhs(1, 9); Rational result; result = lhs * 2; //ok,2不是Rational类型,但可以发生隐式类型转换 result = 2 * lhs; //bad,2不是Rational类型
-
//第二种情况:乘法函数为non-member函数 class Rational { public: ... Rational(int numerator, int denominator); Rational(int num); //这个构造函数使得该类支持从int到Rational的类型转换 } const Rational operator* (const Rational& lhs, const Rational& rhs) { ... } //使用 Rational lhs(1, 9); Rational result; result = lhs * 2; //ok,2不是Rational类型,但可以发生隐式类型转换 result = 2 * lhs; //ok,2不是Rational类型,但可以发生隐式类型转换
-
-
4.8 条款25:考虑写出一个不抛异常的swap函数
- STL库中
std::swap
的典型实现如下代码,这个实现比较平淡。对于某些类,写一个模板特化的swap执行效率会更高。作者介绍了怎么自己写std::swap
的特化版本,这边就不展开了。 -
namespace std { //平淡的std::swap实现 template<typename T> void swap(T& a, T& b) { T temp(a); a = b; b = temp; } }
- STL库中