item18:让接口容易被正确使用,不易被误用
理想情况下应该是如果能编译通过,那么接口一定能实现你想要的,否则就不能编译
假设正在设计一个表示时间数据的类的构造函数:Date(int month, int day, int year);这样会出现两个问题,一是传参的顺序不对,而是所传参数的取值范围不对。
1)用户在传参的时候顺序不对。OK,也许你想到了用结构体,并且不允许隐式转换。如下:
struct Day{ explicit Day(int d):val(d){ } int val;};
同样的对Month和Year定义类似的结构体,这样看起来貌似可以解决问题了。
PS:把Day,Month,Year放在一个完全封装的数据中要比这样单独的定义一个结构体要好,详见item22.
Date d(30, 3, 1995); // error! wrong types Date d(Day(30), Month(3), Year(1995)); // error! wrong types Date d(Month(3), Day(30), Year(1995)); // okay, types are correct
2)用户在传参的时候数据范围不对。怎样才能让一个变量合理的表示它应该有的取值范围呢。比如说这里只有12个Month。OK,可以这样来定义:
class Month { public: static Month Jan() { return Month(1); } // functions returning all valid static Month Feb() { return Month(2); } // Month values; see below for ... // why these are functions, not objects,因为非局部的静态变量的初始化顺序没法保证。 这样保证用的时候变量都已经得到初始化了 static Month Dec() { return Month(12); } ... // other member functions private: explicit Month(int m); // prevent creation of new Month values ... // month-specific data };
然后直接调用Date d(Month::Mar(), Day(30), Year(1995));就OK了。
3)尽量让你设计的类型跟built-in types保持一致。避免无端与built-in types不兼容,其实就是提供行为一致性的接口。
4)任何接口如果如果要求客户必须记得做某些是,就是有着"不正确使用"的倾向,因为客户可能会忘记做那件事。比如说item13中动态分配的时候,为了避免内存的泄露,我们将返回的指针交给一个只能指针来负责内存的释放。
std::tr1::shared_ptr<Investment> createInvestment();
但万一客户忘记了使用智能指针怎么办。这里比较好的设计原则就是先发制人,令其直接返回一个智能指针。这样就几乎完全消除了忘记释放资源的可能性了。
5)又如item14中所言,tr1::shard_ptr允许绑定一个资源释放函数,但这有可能导致"企图使用错误的资源析构函数"。比如说我们想使用的资源析构函数是getRidOfInvestment,而不是delete,为了避免错误,我们可以返回一个将getRidOfInvestment绑定为删除器的tr1::shared_ptr指针。这样我们就试图在CreateInvestment中创建一个null的tr1::shared_ptr并以getRidOfInvestment作为删除器。
像这样:std::tr1::shared_ptr<Investment> pInv(0, getRidOfInvestment);
但是这样是不能编译通过的,因为0是int型,不能转化为指针,就算可以转化,这样也不好。这里可以使用强制转化来实现。这样代码就变成了:
std::tr1::shared_ptr<Investment> createInvestment() { std::tr1::shared_ptr<Investment> pInv(static_cast<Investment*>(0),getRidOfInvestment); pInv= ...;//指向一个正确的对象; return pInv; }
6)当然,如果被pInv管理的原始指针可以在建立pInv之前先确定下来,那么"将原始指针传给pInv构造函数"会比"先将pInv初始化为null再对它做一次赋值操作"要好,详见item26.
7)另外一个tr1::shared_ptr的一个很好的性质是:它会自动使用它的"每个指针专属的删除器",因而消除另一个潜在客户错误——"cross-DLL problem"。这个问题发生于"对象在一个动态链接库DLL中被new创建,却在另一个动态链接库DLL中被delete销毁".在许多平台上,这一类"跨DLL的new/delete的成对运用会导致runtime error。tr1::shared_ptr可以避免这个问题,因为它缺省的删除器是来自"tr1::shared_ptr被创建的那个DLL"的delete。也就是说被创建的那个tr1::shared_ptr可被传递给任何其他的DLLs,无需在意"cross-DLL problem".这个tr1::shared_ptrs会追踪记录,当资源的应用次数变成0时,调用绑定好的DLL's delete。