假设正在为一个表示时间日期的类设计构造函数:
class Date {
public:Date(int month, int day, int year);...
};
Date d(30, 3, 1995); // 糟糕! 应该是 “3, 30” , 而不是 "30, 3"
Date d(2, 30, 1995); // 糟糕! 应该是 "3, 30" , 而不是 "2, 30"
引入新的类型,可以避免许多客户的错误:
struct Day {explicit Day(int d) :val(d) {}int val;
};
struct Month {explicit Month(int d) :val(d) {}int val;
};
struct Year {explicit Year(int d) :val(d) {}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)); // 正确, 参数类型正确
只有12个有效的月份值,Month类型应该反映这一点。一种方法是使用枚举来表示月份,但枚举的类型安全性不高。例如,枚举可以像int一样使用(参见条款2)。更安全的解决方案是预先定义所有有效月份的集合:
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));
任何要求客户小心的接口都容易被错误使用,因为客户可能会忘记:
Investment* createInvestment(); // 来之条款13; 为简单起见,省略了参数
这会导致至少两种类型的客户错误:没有删除指针,以及多次删除同一个指针。
一个更好的接口决策是,让工厂函数返回一个智能指针:
std::shared_ptr<Investment> createInvestment();// 错误!尝试使用自定义删除器创建一个null shared_ptr;
std::shared_ptr<Investment> pInv(0, getRidOfInvestment);std::shared_ptr<Investment> pInv(static_cast<Investment*>(0), getRidOfInvestment); // 可以std::shared_ptr<Investment> createInvestment()
{std::shared_ptr<Investment> retVal(static_cast<Investment*>(0),getRidOfInvestment);retVal = ...; // 让retVal指向正确的对象return retVal;
}
shared_ptr可以解决“跨DLL问题”。当在一个DLL中使用new创建对象,但在另一个DLL中delete时,会出现此问题。shared_ptr避免了这个问题,因为它的默认删除器来自创建shared_ptr的同一个DLL中。例如,如果Stock是Investment的派生类:
std::shared_ptr<Investment> createInvestment()
{return std::shared_ptr<Investment>(new Stock);
}
- 好的接口易于正确使用,而不容易错误使用。应该在所有的接口中努力实现这些特性。
- 促进正确使用的方法包括:接口的一致性和与内置类型的行为兼容性。
- 防止错误的方法包括创建新类型、限制类型上的操作、限制对象值以及消除客户资源管理责任。
- shared_ptr支持自定义删除器。这防止了跨dll问题,可以用来自动解锁互斥锁(见条款14)等