条款18:让接口容易被正确使用,不易被误用

32 阅读2分钟
程序设计中经常遇到的便是如何设计接口,让用户能够直观的、快速的了解并上手使用接口。让接口容易被正确使用,不易被误用便是本条款所强调的。
理想上,如果客户企图使用某个接口而却没有获得他所预期的行为,这个代码不应该通过编译;如果代码通过了编译,他的作为就该是客户所想要的。

比如这样一个表示时间的类:

class Date {
public:
    Date(int month, int day, int year);
};

如果我们不对接口做一些“强制性“的约束,该接口就可能被误用:

Date date(2022, 2, 25);	//传参顺序不对
Date date(31, -1, 0);	//参数非法

虽然上述调用可以通过编译,但却可能与用户意图违背。为此我们可以采用类型系统:

class Month {
public:
    explicit Month(int m) : month(m) {

    }
private:
    int month;
};

class Day {
public:
    explicit Day(int d) : day(d) {

    }
private:
    int day;
};

class Year {
public:
    explicit Year(int y) : year(y) {

    }
private:
    int year;
};

class Date {
public:
    Date(const Month &m, const Day &d, const Year &y);
};

这时,我们对用户使用该类的方式做了一些约束,比如:

Date date(2022, 2, 25);				//报错,explicit禁止隐式类型转换
Date date(Day(25), Month(2), Year(2022));//报错,传参顺序不对
Date date(Month(2), Day(25), Year(2022));//正确

虽然我们对传参的顺序,方式做了一定的约束,但是还是避免不了传入非法值,比如:

Date date(Month(22), Day(25), Year(2022));

月份为22显然不合法。为此,我们可以预先定义所有有效的值(即告知用户,只能使用这几个值哦):

class Month {
public:
    static Month Jan() { return Month(1); }
    static Month Feb() { return Month(2); }
    //...
private:
    explicit Month(int m);
};

再使用时,只能这样,这样总不会写错了吧?:

Date date(Month::Jan(), Day(25), Year(2022));

预防用户错误的另一个做法是,限制类型内什么事可做,什么事不可做。常见的限制是加上const。 比如:我们重载了*并且返回值被const限定,当用户一不小心将==写成=时,程序可以告知用户,唉?你这里是不是要做比较?而不是赋值??

if (a * b = c) // ? 

后面部分理解不是很透彻,再续。 关于shared_ptr删除器的使用:

void del(int *p) {
	cout << "del" << endl;
}

void func() {
	shared_ptr<int> p(new int(3), del);
	cout << p << endl;

	shared_ptr<int> p1(new int(4), [](int *p) { cout << p << endl; cout << "lambda" << endl; });
	cout << p1 << endl;
}