EC++学习笔记(四) 设计与声明
条款18:让接口容易被正确使用,不易被误用
必须考虑客户可能做出什么样的错误(防御式编程)
std:shared_ptr会自动使用它的"每个指针专属的删除器",消除了"cross-DLL problem"(对象在DLL中被 new 创建,却在另一个DLL中 delete 销毁)
条款19:设计 class 犹如设计 type 类型系统
条款20:宁以 pass-by-reference-to-const 代替 pass-by-value
C语言永远是pass-by-value,C++默认是pass-by-value,函数参数都是以实际参数的副本拷贝为初值,函数调用的结果是函数返回值的一个副本拷贝
注意C语言中传指针方式,传指针具有引用语义,对象复制以后并没有分离,而是共享关联同一资源
采用pass-by-reference-to-const 效率高(无对象拷贝),是多态前提(基类引用),也可以避免基类和派生类之间的对象切割
条款21:必须返回局部对象时,禁止返回其reference
const Rational& operator*(const Rational& lhs, const Rational& rhs) { Rational result(lhs.n * rhs.n, lhs.d * rhs.d); return result; }
这个函数返回一个reference指向result,但result是local对象,local对象在函数退出前已经被销毁了!!!
任何函数如果返回一个reference指向某个local对象都将一败涂地
任何函数如果返回一个指针指向某个local对象同样将一败涂地
const Rational& operator*(const Rational& lhs, const Rational& rhs) { static Rational result; result = ...; return result; }
这个函数返回static对象,不具有多线程安全性,并行计算时极易出错
必须返回对象时,就让函数pass-by-value返回一个新对象
inline const Rational operator*(const Rational& lhs, const Rational& rhs) { Rational result(lhs.n * rhs.n, lhs.d * rhs.d); return result; }
这个函数以pass-by-value方式返回值语义对象,值语义对象复制之后两者分离,所以函数结束后获得的仍然是正确的对象
总结:绝不要返回reference或pointer指向一个local对象
条款22:将成员变量声明为 private
客户唯一能够访问对象成员变量的方法就是通过存取函数get()和set()
成员变量的封装性与"成员变量的内容改变时所破坏的代码量"成反比
条款23:宁以non-member、non-friend 替换member函数(本人不认同)
条款24:操作符重载时必须选择设置为类成员还是普通的非成员函数
操作符重载时,选择成员还是非成员实现有一些指导原则:
1.赋值(=)、下标([])、调用( () )、成员访问(->)必须定义为类成员,否则编译出错
2.复合赋值操作符(+=)通常定义为类成员
3.对称操作符(算术运算符、关系运算符)通常选择为non-member函数实现
注:操作符选择类成员实现时,显式形参数比操作数数目少1,因为当操作符为成员函数时,this 指向左操作数
条款25:考虑写一个不抛异常的 swap 函数
STL提供的标准swap算法如下:
namespace std { template<typename T> void swap(T& a, T& b) { T temp(a); a = b; b = temp; } }
缺省版本设计到对象复制,有时效率低下。但是考虑如下:
class WidgetImpl{ public: ... private: int a, b, c; std::vector<double> v; }; class Widget{ public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) { ... *pImpl = *(rhs.pImpl); } private: WidgetImpl* pImpl; };
一旦置换两个Widget对象,唯一需要做的就是置换其pImpl指针,可以考虑模板特化技术
namespace std{ template<> void swap<Widget>(Widget& a, Widget& b) { swap(a.pImpl, b.pImpl); //目前还无法编译通过 } }
上述代码中 template<> 表示它是 std:swap的一个全特化版本,函数名之后的<Widget>表示这一特化版本只针对"T是Widget"而设计(偏特化)
上述代码不能通过编译,因为a.pImpl和b.pImpl都是 private,所以进行如下改进:
class Widget{ public: ... void swap(Widget& other) { //绝不可以抛出异常 using std::swap; //逼迫编译器使用std::swap(因为偏特化版本swap还未实现完成) swap(pImpl, other.pImpl); //此时使用的是std::swap } } namespace std{ template<> void swap<Widget>(Widget& a, Widget& b) { a.swap(b); } }
一旦编译器看到对swap调用时,就会查找适当的swap调用,如果没有T专属之swap存在,编译器就会使用std内的swap
std::swap(obj1, obj2); //错误的swap调用方式,强迫编译器总是调用std内部的swap版本 swap(obj1, obj2); //正确的swap调用方式,不带任何的命名空间修饰
c++只允许对类模板进行偏特化,不能对函数模板进行偏特化