条款18:让接口容易被正确使用,不易被误用
Make interferences easy to use correctly and hard to use incorrectly.
本章分为两个部分。
接口
接口,是C++之本。function接口、class接口、template接口…每一种都是与代码进行交互的手段。
想要开发出一个“容易被正确使用,不易被误用”的接口,首先就需要明白使用这样的接口可能会产生怎样的错误?
举个例子,假设我们为一个用来表示日期的class设计构造函数:
class Date {
public:
Date(int month, int day, int year);
...
};
虽然这样定义的构造函数乍一看没什么问题,但其实在调用时,却可能会产生以下两个错误:
(1)错误的传递参数次序:
Date d(30, 3, 1995); //把月和日传递反了
(2)传递一个无效的月份或天数:
Date d(2, 30, 1995); //二月有30号??
想要解决这样的问题,类型系统(type system)是一个关键办法。我们使用外覆类型(wrapper type)来区别这样的天数、月份和年份,然后在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, 1995); //错误的类型!
Date d(Day(30), Month(3), Year(1995)); //错误的类型!
Date d(Month(3), Day(30), Year(1995)); //正确了!
以函数替代对象
当我们确定好类型的定义,限制它们的取值也是非常重要。比如,月份的取值只能是1-12。
方法之一,就是利用enum表现月份。不过enum并不具备类型安全性。因此,比较安全的解法是预先定义所有有效的Months:
class Month {
public:
static Month Jan() { return Month(1): } //函数,返回有效的月份
static Month Feb() { return Month(2); }
...
static Month Dec() { return Month(12);}
... //其他的成员函数
private:
explicit Month(int m) //阻止生成新的月份
...
};
Date d(Month::Mar(), Day(30), Year(1995));
预防错误的另一种方法,限制类型内什么事情可以做,什么不可以做。常见的限制是加上const。在前面的条款3只,就对const的作用进行了说明。