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

如果客户企图使用某个接口而却没有获得他所预期的行为,这个代码就不该通过编译,如果代码通过了编译,

它的作为就该是客户所想要的。

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

第一,以错误的次序传递参数:

Date(30, 3, 1998);//应该是“3,30”而不是“30,3”

第二,传递一个无效的月份或天数:(打岔一个键)
Date(2, 30, 1998);//应该是“3,30”而不是“2, 30”

许多客户端错误可以通过导入新的类型而获得预防:

简单的外覆(wrapper types)类型来区别天数、月份、和年份,然后于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, 1998); //错误,不正确类型
Date d(Day(30), Month(3), Year(1998));//错误,不正确类型
Date d(Month(3), Day(30), Year(1998));//正确,正确类型

一旦正确的类型就定位,限定其值有时候是通情达理的:enums不具备我们希望的类型安全性;比较安全的做法是预先定义

有效的Months:

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

Date d(Month::Mar(); Day(3), Year(1998));

预防客户错误的另一个办法是:限制类型内什么事可做,什么事不能做。常见的限制是加上const。

另一个一般性准则是:“除非有好理由,否则应该尽量令你的types的行为与内置types一致”。一旦怀疑,就拿ints做范本。

避免与内置类型不兼容,真正的理由是为了提供行为一致的接口,stl容器的接口十分一致,使得它们很容易被使用。

任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,因为客户可能会忘记做那件事。

Investment* createInvestment();//必须要求客户删除返回的指针,

//客户可能没有删除指针,或者删除指针超过一次。

较佳的设计原则是先发制人,令factory函数返回一个只能指针:

std::tr1::shared_ptr<Investment> createInvestment();

如果设计者希望那些从createInvestment 取得的Investment*指针不是用delete而是一个名为getRidOfInvestment的函数:

std::tr1::shared_ptr<Investment> createInvestment()
{
    std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment());
    retVal = ...; //令retval 指向正确对象
    return retVal;
}

tr1::shared_ptr有一个特别的好的性质:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误:

所谓的“cross-Dll problem”。对象在一个dll中被new,却在另一个dll内被delete。在许多平台上,这一类“跨dll的new/delete成对运用”会导致运行期错误。shared_ptr没有这个问题,因为它缺省的删除器是来自“shared_ptr诞生所在的那个Dll”的delete。例如Stock是派生自Investment的:

std::tr1::shared_ptr<Investment> createInvestment()
{
    return std::tr1::shared_ptr<Investment>(new Stock);
}

返回的那个tr1::shared_ptr可被传递给任何其他的DLLs,无需在意“cross-DLL problem”。这个指向Stock的tr1::shared_ptr会追踪记录“当Stock的引用次数变成0时该调用的那个DLL's delete”。

1.“促进正确使用”的办法包括接口一致性, 与内置类型的行为兼容

2.“防止误用”的方法包括,建立新类型限制类型上的操作束缚对象值、以及消除客户的资源管理责任

3.tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁(mutexes)等等

posted @ 2012-01-16 22:32  lidan  阅读(345)  评论(0编辑  收藏  举报