❀“促进正确使用”的方法包括接口的一致性,以及与内置类型的行为兼容。
❀“阻止误用”的方法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
❀ tr1::shared_ptr支持订制型删除器,可防范DLL问题,可被用来自动解除互斥锁等。
public:
Date(int month,int day,int year);
...
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);
...
};
继续, 限制类型上的操作,束缚对象值
例如上面的例子,为了保证客户输入一年12个有效月,可以利用enum表现月份,但enum不具备类型安全性(例如一个也enums可被拿来当一个ints使用)。
比较安全的解法是预先定义所有有效的Months:
class Month{
public:
static Month Jan(){return Month(1);}
...
private:
explicit Month(int m);//抑制隐式转换,阻止生成新的月份,这是月份专属数据。
};
客户:Date d(Month::Jan(),Day(30),Year(2013));
预防客户错误的另一个方法是加上 const, 例如条款3中“以const修饰operator*的返回类型”可阻止客户因“用户自定义类型”而犯错。
if(a * b = c) ...//这里其实是要做一次a * b == c
尽量令types的行为与内置types一致。
->客户已经知道像int这样的内置接口的行为,所以应让你的types行为尽量与这些表现相同。
提供行为一致的接口
->例:STL容器都有一个名为size的成员表示目前容器内有多少对象。
而java的数组长度使用的是lenth函数,List长度使用的是size。稍微有点混乱吧,记忆起来也容易搞混-_-!
.Net也是的, Arrays->length ArrayLists->Count。。。
任何接口如果要求客户必须记得做某些事情,就有着“不正确使用”的倾向。
->例:条款13中导入一个factory函数,返回一个指向指针Investment继承体系内的一个动态分配对象:
Investment* createInvestment();
为了避免资源泄露,先发制人,令factory函数返回智能指针:
std::tr1::shares_ptr<Investment> createInvestment();
这边强迫客户将返回值存放在智能指针中,几乎消弭了客户忘记删除对象的可能性。
如果期待客户将上面的指针传递给一个名为getRidOfInvestment的函数,而不是直接使用delete。
设计者可以针对此问题返回一个将getRidOfInvestment绑定为删除器的tr1::shared_ptr:
//建立一个null shared_ptr并以getRidOfInvestment为删除器
std::tr1::shares_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),getRidOfInvestment);
retVal = ...
return retVal;
}
*如果pInv管理的原始指针可以在建立pInv之前先确定下来那么:“将原始指针传给pInv构造函数”会比“先将pInv初始化为null再进行赋值操作”更好。见条款26.
tr1::shared_ptr可防范cross-DLL problem问题。
cross-DLL problem:对象在动态程序库DLL中被new,却在另一个DLL钟被delete。像这种跨DLL之new/delete会导致运行期错误。
tr1::shared_ptr缺省的删除器来自tr1::shared_ptr诞生的那个DLL的delete所以不存在cross-DLL problem。
比如 Stock派生自Investment的实现如下:
std::shared_ptr<Investment> createInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}
返回的智能指针可被传递给任何其他DLLs,无需注意cross-DLL problem。这个指向Stock的shared_ptr会追踪记录“当Stock的引用次数变成0时该调用的那个DLL的delete”。
❀“阻止误用”的方法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
❀ tr1::shared_ptr支持订制型删除器,可防范DLL问题,可被用来自动解除互斥锁等。
想开发一个“易被正确使用,不易被无用”的接口,首先必须考虑客户可能做出什么样的错误。
比如设计一个表示日期的class设计构造函数
public:
Date(int month,int day,int year);
...
};
考虑客户调用的时候容易犯的错误:
①传递错误的参数次序
②传递无效的日期
解决的方法:建立新类型-----设计时导入外覆类型来区别天数、月份和年份。如下: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(Month(3),Day(30),Year(2013)); //OK!
继续, 限制类型上的操作,束缚对象值
例如上面的例子,为了保证客户输入一年12个有效月,可以利用enum表现月份,但enum不具备类型安全性(例如一个也enums可被拿来当一个ints使用)。
比较安全的解法是预先定义所有有效的Months:
class Month{
public:
static Month Jan(){return Month(1);}
...
private:
explicit Month(int m);//抑制隐式转换,阻止生成新的月份,这是月份专属数据。
};
客户:Date d(Month::Jan(),Day(30),Year(2013));
预防客户错误的另一个方法是加上 const, 例如条款3中“以const修饰operator*的返回类型”可阻止客户因“用户自定义类型”而犯错。
if(a * b = c) ...//这里其实是要做一次a * b == c
尽量令types的行为与内置types一致。
->客户已经知道像int这样的内置接口的行为,所以应让你的types行为尽量与这些表现相同。
提供行为一致的接口
->例:STL容器都有一个名为size的成员表示目前容器内有多少对象。
而java的数组长度使用的是lenth函数,List长度使用的是size。稍微有点混乱吧,记忆起来也容易搞混-_-!
.Net也是的, Arrays->length ArrayLists->Count。。。
任何接口如果要求客户必须记得做某些事情,就有着“不正确使用”的倾向。
->例:条款13中导入一个factory函数,返回一个指向指针Investment继承体系内的一个动态分配对象:
Investment* createInvestment();
为了避免资源泄露,先发制人,令factory函数返回智能指针:
std::tr1::shares_ptr<Investment> createInvestment();
这边强迫客户将返回值存放在智能指针中,几乎消弭了客户忘记删除对象的可能性。
如果期待客户将上面的指针传递给一个名为getRidOfInvestment的函数,而不是直接使用delete。
设计者可以针对此问题返回一个将getRidOfInvestment绑定为删除器的tr1::shared_ptr:
//建立一个null shared_ptr并以getRidOfInvestment为删除器
std::tr1::shares_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),getRidOfInvestment);
retVal = ...
return retVal;
}
*如果pInv管理的原始指针可以在建立pInv之前先确定下来那么:“将原始指针传给pInv构造函数”会比“先将pInv初始化为null再进行赋值操作”更好。见条款26.
tr1::shared_ptr可防范cross-DLL problem问题。
cross-DLL problem:对象在动态程序库DLL中被new,却在另一个DLL钟被delete。像这种跨DLL之new/delete会导致运行期错误。
tr1::shared_ptr缺省的删除器来自tr1::shared_ptr诞生的那个DLL的delete所以不存在cross-DLL problem。
比如 Stock派生自Investment的实现如下:
std::shared_ptr<Investment> createInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}
返回的智能指针可被传递给任何其他DLLs,无需注意cross-DLL problem。这个指向Stock的shared_ptr会追踪记录“当Stock的引用次数变成0时该调用的那个DLL的delete”。