一. 内容
- 准则:
如果客户使用某个接口通过了编译,那么它的行为就是应该是客户想要的
。 - 所以我们应该
考虑客户可能会遇到什么样的错误
。 - 一个例子
这些示例可能看起来有些愚蠢,但是它们并不罕见。class Date { public: Date(int mMonth,int mDay,int mYear): Month(mMonth),Day(mDay),Year(mYear){} private: int Month; int Day; int Year; }; inline void BeginPlay() { Date DateOne(30,3,1995);//oh,应该是3,30 Date DateTwo(2,30,1995);//oh,应该是3, 30或者不存在2,30 }
- 这些客户端错误可以通过
导入新的类型
而获得预防。
令Month,Day,Year成为成熟且经充分锻炼的 Classes 或者 Structs,对预防接口被误用有神奇效果。struct Month { explicit Month(int mValue):Value(mValue){} int Value; }; struct Day { explicit Day(int mValue):Value(mValue){} int Value; }; struct Year { explicit Year(int mValue):Value(mValue){} int Value; }; class DatePro { public: DatePro(const Month& mMonth,const Day&mDay,const Year& mYear): Month(mMonth),Day(mDay),Year(mYear){} private: Month Month; Day Day; Year Year; }; inline void BeginPlayTwo() { DatePro DateOne(30,3,1995);//错误,不正确的类型 DatePro DateTwo(Month(3),Day(30),Year(1995));//ok,类型正确 }
- 一旦类型正确,有时候
限制其取值
是合理的。class LimitedMonth { public: static LimitedMonth Jan(){return LimitedMonth(1);} static LimitedMonth Feb(){return LimitedMonth(2);} static LimitedMonth Mar(){return LimitedMonth(3);} static LimitedMonth Apr(){return LimitedMonth(4);} static LimitedMonth May(){return LimitedMonth(5);} static LimitedMonth Jun(){return LimitedMonth(6);} static LimitedMonth Jul(){return LimitedMonth(7);} static LimitedMonth Aug(){return LimitedMonth(8);} static LimitedMonth Sept(){return LimitedMonth(9);} static LimitedMonth Oct(){return LimitedMonth(10);} static LimitedMonth Nov(){return LimitedMonth(11);} static LimitedMonth Dec(){return LimitedMonth(12);} private: explicit LimitedMonth(int mValue):Value(mValue){} int Value; };
- 预防客户错误的另一个方法是:限制类型内什么事情可做,什么事情不能做。常见的方式是加上 const,如条款3。
- 还有一个一般性准则是:
除非有好理由,否则尽量令接口行为和内置接口行为保持一致
。比如 STL 容器的接口都十分一致,这使得它们可以被容易的调用。 任何接口如果要求客户必须记得做某些事情,就存在不正确使用的隐患
。条款13在获取资源返回的是原始指针,假如客户忘记使用智能指针进行管理,怎么办呢?最好的做法是先发制人,令获取资源的接口返回智能指针。
而对于不同的资源,智能指针默认的资源释放行为可能不适用,这也应该提前为返回的智能指针写好删除函数,避免客户误操作。
shared_ptr 有个比较好的性质就是,自动使用它专属的删除函数,消除了cross-DLL
问题:对象在动态连接程序库(DLL)被 new 创建,却在另一个 DLL 内被另一个delete销毁。shared_ptr 保证对象会使用原来所在单元的 delete。
二. 总结
- 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
- 促进正确使用的方法包括接口的一致性,以及与内置类型的行为兼容。
- 阻止误用的方法包括建立新类型,限制类型上的操作,束缚类型值,以及消除客户的资源管理责任。
- trl::shared_ptr 支持定制型删除器(custom deleter)。这可防范 DLL 问题,可被用来自动解除互斥锁(mutexes,见条款14)等等。