Effective C++学习笔记总链接
改善程序与设计的55个具体做法学习笔记-每日1条
条款18:让接口容易被正确使用,不易被误用
【技巧】
1. 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
2. “促进正确使用”的办法包括接口的一致性,以及与内存类型的行为兼容。
3. “阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
4. shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁等等。(没读懂。。。下次再细品)
欲开发一个“容易被正确使用,不容易被误用”的接口,首先必须考虑客户可能做出什么错误。
且看下面代码:
class Date
{
public:
Date(int month, int day, int year);
...
};
上述代码,客户会犯什么错误呢?
乍一看,这个接口通情达理,但是它的客户很容易犯下至少两个错误。
- 一、也许会以错误的次序传递参数
Date d(30, 3, 1995); // 应该是 3, 30
- 二、可能传递一个无效的月份或天数
Date d(2, 30, 1995); // 应该是3, 30
解决办法
导入简单的外覆类型来区别天数、月份和年份,然后在Date构造函数中使用这些类型:
class Day // Day class
{
public:
explicit Day(int d):val(d){}
private:
int val;
};
class Month // month class
{
public:
explicit Month(int m):val(m){}
private:
int val;
};
class Year // year class
{
public:
explicit Year(int y):val(y){}
private:
int val;
};
class Date
{
public:
Date(const Month& m, const Day& d, const Year& y);
...
}
Date d(30, 3, 1995); // error
Date d(Month(3), Day(30), year(1995)); // ok,类型正确
一旦正确的类型就定位,限制其值有时候是通情达理的。
比较安全的解法是预先定义所有有效的Month
完美解法
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
限制类型内,什么事可以做,什么事不可做,常见的限制是加const
让type容易被正确使用
除非有好理由,否则应该尽量令你的type的行为与内置type一致。(比如和int类型的行为一致)
例如: a, b 为 int,那么a*b赋值并不合法,除非你有好的理由,否则应该让你的type 也有相同的表现。
切记,任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,因为客户可能会忘记做那件事。
Investment* createInvestment();
上述代码至少开启两个客户错误的机会:没有删除指针,或删除同一个指针超过一次。
较佳接口的设计原则是先发制人
std::shared_ptr<Investment> createInvestment();
上式几乎打消了忘记删除底部Investment对象(当它不再被使用时)的可能性
同时shared_ptr可以消除“cross-DLL problem”
这个问题发生在“对象在动态链接程序库(DLL)中被创建,却在另一个DLL内被delete销毁”