《Effective C++》学习笔记——条款18

本文是《Effective C++》学习笔记的条款18,讨论如何设计接口以使正确使用变得容易,错误使用变得困难。文章强调了接口一致性、类型安全性、资源管理的重要性,并通过例子介绍了如何利用tr1::shared_ptr防止资源泄漏和跨DLL问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

***************************************转载请注明出处:http://blog.youkuaiyun.com/lttree********************************************





四、Designs and Declarations


▪ 断断续续到了第四章了,这一章中我们将对 良好C++接口 的 设计和声明 发起攻势。

▪ 我们以  或许最重要、适合任何接口设计的 一个准则作为开端——"让接口容易被正确使用,不容易被误用"

▪ 通过这个准则设立一个舞台,让其他更专精的准则对付一大范围的题目,包括: 正确性、高效性、封装性、维护性、延展性,以及 协议的一致性。

 

 

Rule 18:Make interfaces easy to use correctly and hard to use incorrectly.

规则 18:让接口容易被正确使用,不易被误用





1.现象

C++ 一直在 接口之海 漂浮。function接口、class接口、template接口……每一种接口都是客户与你的代码互动的手段。

理想上,如果客户企图使用某个接口而却没有获得他所预期的行为,这个代码不该通过编译:如果代码通过了编译,它的作为就应该是客户所想要的。



2.开发" 容易被正确使用,不易被误用 "的接口

>首先,必须考虑客户可能做出什么样的错误。

// 先看一个表现日期的类的构造函数
class Date  {
public:
    Date(int month,int day,int year);
    ...
};

上面代码,乍看一下,是没有问题的,可是 暗藏杀机 啊!

① 他们也许会以错误的次序传递参数:Date d(30,3,1995);

② 他们可能传递一个无效的月份或天数:Date d(2,30,1995);


>防范“ 不值得拥有的代码 ” 上, 类型系统(type system )是你的主要同盟国。

1.我们可以导入 外覆类型(wrapper types )来区别 天数、月份、年份。

<span style="font-family:Comic Sans MS;">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);
    ...
};</span>

PS: C++提供了 关键字 explicit,可以阻止不应该允许的经过 转换构造函数 进行的隐式转换的发生。声明为explicit的 构造函数 不能在隐式转换中使用。

2.一旦正确的类型就定位,限制其值有时候是通情达理的,比如,可以用enum来制定月份,但enum不具备我们希望拥有的类型安全性,这有比较安全的解法,就是预先定义所有有效的Months

<span style="font-family:Comic Sans MS;">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) );</span>


>预防客户错误的另一个办法,限制类型内什么事可以做,什么事不能做

—1.常见的限制 只是 加上 const。

—2.除非有好理由,否则应该尽量令你的types 的行为 与内置 types 一致。

很少有其他性质比得上“一致性”更能导致“接口容易被正确使用”,也很少有其他性质比得上“不一致性”更加剧接口的恶化。

这里就有一点小比较

▪ C++ 的STL容器的接口十分一致(虽然并不是完美的一致),比如每个STL容器都有一个名为 size 的成员函数,作用是告诉用户目前容器内有多少对象。

▪ JAVA ,它允许对数组使用 length property,对Strings 使用 length method, 对 Lists 使用 size method

▪ .NET 对 Arrays 有个 property名为 Length,ArrayLists 有个 property 名为 Count 

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

比如条款13导入的工厂函数(factory function),它返回一个指针指向 Investment 继承体系内的一个动态分配对象:

Investment* createInvestment();

为避免资源的泄露,createInvestment 返回的指针必须删除,但那至少开启了两个客户错误机会:没有删除指针,或删除同一个指针超过一次。

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

实际上,返回 tr1::shared_ptr 让接口设计者得以阻止一大群客户犯下的 资源泄漏 错误。

—4.假设 class 设计者期许那些“ 从 createInvestment 取得 Investment* 指针”的客户将 该指针传递给一个名为 getRidOfInvestment的函数,而不是直接在它身上使用delete(就是用 delete  替换 getRidOfInvestment )。

它可以返回一个“将getRidOfInvestment 绑定为删除器(deleter )”的 tr1::shared_ptr

tr1::shared_ptr提供的某个构造函数接受两个实参:一个是被管理的指针,另一个是引用次数变成0时 将被调用的“删除器”。

但是有一点,tr1::shared_ptr 构造函数坚持其第一参数必须是指针,而0不是指针,是个int。虽然它可以被转换成指针,但是这不是特别好,所以用 转型(cast)来解决这个问题:

<span style="font-family:Comic Sans MS;">std::tr1::shared_ptr<Investment> pInv( static_cast<Investment*>(0),getRidOfInvestment);</span>

因此,如果要实现createInvestment使它返回一个 tr1::shared_ptr 并夹带 getRidOfInvestment 函数作为删除器,代码看起来就是这样的:

<span style="font-family:Comic Sans MS;">std::tr1::shared_ptr<Investment> createInvestment()
{
    std::tr1::shared_ptr<Investment> retVal(static_cast<Investment* >(0),getRidOfInvestment);
    retVal = ... ;    // 令 retVal 指向正确对象
    return retVal;
}</span>

如果被 pInv 管理的原始指针(raw pointer ) 可以在建立 pInv之前 先确定下来,那么 "将原始指针传给pInv构造函数" 会比 "先将pInv初始化为null 再对它做一次赋值操作 " 为佳。(具体原因,将会在条款26给出)

—5 tr1::shared_ptr 有一个特别好的性质:它会自动使用它的" 每个指针专属的删除器 ",因而消除另一个潜在的客户错误:所谓的“ cross-DLL problem ”(就是一个DLL中new,但是在另一个DLL中 delete )。



3.About This Rule

关于这个条款,要明确一下,本条款并非特别针对 tr1::shared_ptr ,而是为了“ 让接口更容易被正确使用,不容易被误用 ”。为什么本条款对 tr1::shared_ptr 讲了这么这么多? 因为它 是如此的容易消除错误,值得我们使用。 最常见的 tr1::shared_ptr 实现来自Boost。Boost的shared_ptr 比 原始指针 大 且 慢,而且使用辅助动态内存。在许多应用程序中这些额外的执行成本并不显著,然而其“ 降低客户错误 ”的成效却是每个人都看得到。



4.请记住

★  好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质

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

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

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




***************************************转载请注明出处:http://blog.youkuaiyun.com/lttree********************************************

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值