effective C++ 条款 18:让接口容易被正确使用,不易被误用
如果客户企图使用某个接口而却没有获得他所预期的行为,这个代码就不该通过编译,如果代码通过了编译,
它的作为就该是客户所想要的。
class Date
{
public:
Date(int month, int day, int year);
...
};第一,以错误的次序传递参数:
Date(30, 3, 1998);//应该是“3,30”而不是“30,3”
第二,传递一个无效的月份或天数:(打岔一个键)
Date(2, 30, 1998);//应该是“3,30”而不是“2, 30”
许多客户端错误可以通过导入新的类型而获得预防:
简单的外覆(wrapper types)类型来区别天数、月份、和年份,然后于Date构造函数中使用这些类型:
struct Day{
explicit Day(int d)
: val(d){}
int val;
};struct Month{
explicit Month(int m)
: val(m);
int val;
};struct Year{
explicit Year(int y)
: val(y);
int val;
};class Date
{
public:
Date(const Month& m, const Day& d, const Year& y);
...
};Date d(30, 3, 1998); //错误,不正确类型
Date d(Day(30), Month(3), Year(1998));//错误,不正确类型
Date d(Month(3), Day(30), Year(1998));//正确,正确类型
一旦正确的类型就定位,限定其值有时候是通情达理的:enums不具备我们希望的类型安全性;比较安全的做法是预先定义
有效的Months:
class Month
{
public:
static Month Jan() {return Month(1);}
static Month Feb() {return Month(2);}
static Month Mar() {return Month(3);}
...
static Month Dec() {return Month(12);}
private:
explicit Month(int m);
...
};
Date d(Month::Mar(); Day(3), Year(1998));
预防客户错误的另一个办法是:限制类型内什么事可做,什么事不能做。常见的限制是加上const。
另一个一般性准则是:“除非有好理由,否则应该尽量令你的types的行为与内置types一致”。一旦怀疑,就拿ints做范本。
避免与内置类型不兼容,真正的理由是为了提供行为一致的接口,stl容器的接口十分一致,使得它们很容易被使用。
任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,因为客户可能会忘记做那件事。
Investment* createInvestment();//必须要求客户删除返回的指针,
//客户可能没有删除指针,或者删除指针超过一次。
较佳的设计原则是先发制人,令factory函数返回一个只能指针:
std::tr1::shared_ptr<Investment> createInvestment();
如果设计者希望那些从createInvestment 取得的Investment*指针不是用delete而是一个名为getRidOfInvestment的函数:
std::tr1::shared_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment());
retVal = ...; //令retval 指向正确对象
return retVal;
}
tr1::shared_ptr有一个特别的好的性质:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误:
所谓的“cross-Dll problem”。对象在一个dll中被new,却在另一个dll内被delete。在许多平台上,这一类“跨dll的new/delete成对运用”会导致运行期错误。shared_ptr没有这个问题,因为它缺省的删除器是来自“shared_ptr诞生所在的那个Dll”的delete。例如Stock是派生自Investment的:
std::tr1::shared_ptr<Investment> createInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}返回的那个tr1::shared_ptr可被传递给任何其他的DLLs,无需在意“cross-DLL problem”。这个指向Stock的tr1::shared_ptr会追踪记录“当Stock的引用次数变成0时该调用的那个DLL's delete”。
1.“促进正确使用”的办法包括接口一致性, 与内置类型的行为兼容。
2.“防止误用”的方法包括,建立新类型、限制类型上的操作、束缚对象值、以及消除客户的资源管理责任。
3.tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁(mutexes)等等