Effective C++(18) 让接口更容易被正确使用,不易被误用

问题聚焦:
    从这个条款开始,我们把注意力转移到软件设计和声明上来,具体的说就是,C++接口的设计和声明。
    所谓软件设计,就是以一般习惯的构想开始,演变成细节的实现,最终开发针对性的特殊接口。
    接口的设计和声明的一个最基本的准则是:让接口容易被正确使用,不容易被误用。
    引入新类型是预防接口被误用的常见手段之一。


欲开发一个好的接口,首先必须考虑客户可能做出什么样的错误。
来看下面这个例子
class Date
{
public:
    Date(int month, int day, int year);
    ... ...
};
上面这个接口有什么问题呢?(如果你觉得没问题的话,那么就该像我一样老实地看下去。。)
它的客户很容易犯下至少两个错误:
  1. 他们也许会以错误的次序传递参数,如:Date d(30, 3, 1995);
  2. 他们可能传递一个无效的月份或天数,如:Date d(2, 30, 1995);
许多像这类客户端错误,可以通过引入新类型获得预防。
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& , const Day& d, const Year& y);
    ... ...
};

// 使用
Date d(30, 3, 1995);                     // error,类型错误
Date d(Day(30), Month(3), Year(1995));    // error,类型错误
Date d(Month(3), Day(30),  Year(1995));
预防客户错误的另一个办法是,限制类型内什么事可做,什么事不能做。
常见的限制是为函数或者重载操作符的返回值加上const。


让types容易被正确使用,不容易被误用。
下面主要通过智能指针std::tr1::shared_ptr的用法来说明这个思想。

任何接口如果要求客户必须记得做某些事情,就是有着被不正确使用的倾向,因为客户可能会忘记做那件事情。
如下这个工厂函数(参数省略)
Inverstment* createInvestment();
错误倾向:没有删除指针,或删除同一个指针超过两次。
方案:在条款13中已经告诉我们如何将createInvestment的返回值存储与一个智能指针,以防止资源泄漏。
错误倾向:客户忘记使用智能指针。
方案:先发制人——较佳接口的设计原则之一,令工厂函数返回一个智能指针。
std::tr1::shared_ptr<Investment> createInvestment();

回到原始的返回Investment*指针的版本,这时,有的客户希望通过函数getRidOfIvestment来析构这个资源,而不是直接的delete。
错误倾向:企图使用错误的资源析构机制,用delete而不是getRidOfInvestment
方案:使用str1::shared_ptr提供的第二个参数:自定义的删除器
//创建一个null shared_ptr指针,并自带一个删除器
std::tr1::shared_ptr<Investment> pInv(0, getRidOfInvestment);   // error!第一个参数应该是一个指针,而不是int
// 使用转换cast
std::tr1::shared_ptr<Investment> pInv( static_cast<Investment*>(0), getRidOfInvestment );

// 基于上面的定制的tr1::shared_ptr来解决上面的这个错误倾向
std::tr1::shared_ptr<Investment> createInvestment()
{
    std::str1::shared_ptr<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment);
    retVal = ... ;       // 令retVal指向正确对象
    return retVal;
}
错误倾向:cross_DLL problem,即对象在动态连接程序(DLL)中被new创建,却在另一个DLL内被delete销毁。
方案:tr1::shared_ptr没有这个问题,因为它的删除器是自带的(第二个参数默认指定的),所以不会被别的DLL销毁。

tr1::shared_ptr看起来在“降低客户错误”方面的功能很强大,但是其代价就是该指针比原始指针大且慢,而且使用辅助动态内存。
所以如何使用,视使用场景具体判断。



小结:
  • 好的接口很容易被正确使用,不容易被误用。应该在接口设计时努力打到这点。
  • 促进正确使用的办法包括接口的一致性,以及与内置类型的行为兼容
  • 阻止误用的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
  • tr1::shared_ptr支持定制删除器,这可防范DLL问题,可被用来自动解除互斥锁问题。(Effective C++(14) 在资源管理类中小心copying行为

参考资料:
《Effective C++ 3rd》

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。