1、理想上,如果客户企图使用某个接口而却没有获得他所预期的行为,这个代码不该通过编译;如果代码通过了编译,它的作为就该是客服所想要的。
想要开发出来一个“容易被正确使用,不易被误用”的接口,首先必须考虑客户可能做出什么样的错误。
预防客户端错误的手段:
(1)导入新类型。
确定参数类型,使得接口的参数类型不会发生错误。
(2)限定参数的取值范围
- 以函数代替对象。
- enum(不好,能作为int访问)
(3)限制类型内的操作
具体做法:加const。
(4)一致性准则
“让type 容易被正确使用,不容易被误用”的一条准则:避免无端与内置类型不兼容,尽量让你的type与内置类型的type一致。
举例:STL中容器的接口就十分一致:每个容器都有个size的成员函数,它的功能是一致的,即返回目前容器有多少和对象
(5)采用资源管理类
消除客户的资源管理责任。
2、任何接口如果要求用户必须记得做什么事儿,就是有着“被不正确使用”的倾向。例如new出来的东西必须delete掉。
举例:
条款13中的投资类工厂函数
Investment* createInvestment();//投资类工厂函数,返回一个指向动态内存的指针
这个设计,要求客户在使用完动态内存后,必须删除这个指针,而这容易使得客户犯两个错误:
- 没有删除指针。( 造成资源泄露)
- 删除同一个指针超过一次。(造成不确定行为。)
条款13中给出了解决可能造成资源泄露的问题的方案:
即,将工厂函数的返回值内含在一个智能指针当中,把回收资源的责任推给了这个智能指针去做。
但是现在的问题是,假如客户根本就没有将工厂函数返回的指针内含在一个智能指针的意识,也就是说他可能不这么做。那么我们就得想办法,先发制人,让他必须这么做。
具体的做法就是,直接让这个工厂函数返回的就是一个智能指针。
实际上,返回shared_ptr 让接口设计者阻止了一大群客户可能犯的资源泄露错误,因为它一创建起来就指定了资源释放函数。在这个例子中,我们使用的资源是动态内存,需要使用delete释放。然而,假设本身的资源并不是动态内存,那么就不该使用delete取回收,而是使用资源回收函数,而返回一个智能指针,在返回前便可以指针这样的资源回收函数。这实际上也避免了客户错误的进行资源回收的问题。
如何创建一个暂时不指向真实资源的智能指针对象?即,底部指针暂时为null的。
std::tr1::shared_ptr<Investment> pInv(0, getRidOfInvestment); //错误的写法
//正确地创建一个null shared_ptr并携带一个自定的删除器
std::tr1::shared_ptr<Investment> pInv(static_cast<Investment*>(0), getRidOfInvestment);//正确的写法
如果原始指针可以直接确定的话,当然还是直接创建一个初始化的智能指针为好。上述代码,可以创建一个原始指针暂时不能确定,但是删除器可以确定的智能指针。
3、 tr1::shared_ptr的另外一个好性质:解决cross-DLL problem
(1)什么是ross-DLL problem?
这个问题发生于“对象在动态链接库(DLL)中被创建,但却在另一个DLL内被delete销毁”。在一些平台上,这一类“跨DLL的new/delete成对运用”会导致运行期间错误。
(2)tr1::shared_ptr为什么能够解决cross-DLL problem?
因为它默认的删除器是来自“tr1::shared_ptr诞生所在的那个那个DLL”的delete。
(3)举例:
如果Stock派生自Investment,而createInvestment的实现如下:
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”。
4、关于shared_ptr
(1)shared_ptr的用途:
动态内存分配的记录用途、删除器之专属数据(以virtual形式调用删除器。)
(2)优点:
降低用户错误的可能性, 防止资源泄露。
(3)缺点:
它所占内存比普通指针大,且访问速度慢。在多线程程序修改引用次数时蒙受线程同步化开销。
(4)综合:
虽然牺牲了一点内存和速度,但是大大降低了用户错误的可能性。