假如有这样的类:
class Date
{
public:
Date(int month, int day, int year);
...
}
看起来这个接口没错,但是很容易犯下至少两个错误:
Date d(30,3,2019);//错误的次序
Date d(2,30,1995);//应该是3,30,而不是2,30
我们可以通过导入新类型来获得预防比如:
struct Day
{
int val;
explicit Day(int d) :val(d) {}
};
struct Month
{
int val;
explicit Month(int m) :val(m) {}
};
struct Year
{
int val;
explicit Year(int y) :val(y) {}
};
class Date
{
public:
Date(const Month & m, const Day & d, const Year & y);
...
};
Date d(30,3,2019);//错误
Date d(Day(30),Month(3),Year(2019));//错误
Date d(Month(3),Day(30),Year(2019));//正确
一旦正确的类型就定位,限制其值有时候是通情达理的。比如一年只有12个月
方法之一是:使用enum,但enum不具备我们希望拥有的类型安全,比如enum可以拿来当作int使用
比较安全的解法是:预先定义所有有效的Months:
class Month
{
public:
static Month Jan(){return Month(1);}
static Month Feb(){return Month(2);}
...
private:
explicit Month(int m);
};
Date d(Month::Mar(), Day(30), Year(2019));
预防错误的另一个方法是:限制类型内什么事可做,什么事不能做。常见的限制是加上const.
任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,比如条款13的工厂函数:
Investment * createInvestment();
可能会导致两个错误:
没有删除指针,或者删除同一个指针超过一次.
条款13表明如何将createInvestment
返回值存储于一个智能指针,因而将delete
责任推给智能指针。但万一客户忘记了怎么办?
我们可以先发制人,让工厂函数返回一个智能指针:
std::shared_prt<Investment>createInvestment();
假如我们要实现createInvestment
并使它返回一个std::shared_prt,但是我们不想使用delete
而是使用自己定做的删除器,这样一个接口又可能造成另一个错误,就是使用delete
而不是特定的删除器。
针对这个问题,我们也可以先发制人:返回一个绑定删除器的shared_prt.
比如:
std::shared_prt<Investment>p(static_cast<Investment*>(0),getRidOfInvestment);
因此,如果我们要实现createInvestment
使它返回一个std::shared_prt
并夹带getRidOfInvestment
函数作为删除器,代码看起来像这样:
std::shared_prt<Investment>createInvestment()
{
std::shared_prt<Investment>p(static_cast<Investment*>(0),getRidOfInvestment);
p = ...;//令p指向正确对象
return p;
}
如果被p管理的原始指针可以在建立p之前先确定下来,那么我们可以直接将原始指针传给p构造函数会更好。
总结:
1.接口的一致性,以及内置类型的行为兼容可以促进正确使用
2.阻止误用的办法包括建立新类型、限制类型上的操作、束缚对象值,以及消除客户的资源管理责任
3.shared_prt支持定制型删除器。可防范DLL问题。