Effective C++ 学习笔记(十四)
2016-11-26 22:36
302 查看
开发一个“容易被正确使用,不容易被误用”的接口,首先必须考虑客户可能做出什么样的错误。
假设你为一个用来表现日期的class设计构造函数:
class Date{
public:
Date(int month,int day,int year);
};
但它的客户很容易犯至少两个错误,第一,它们会以错误的次序传递参数:
Date d(30,3,1995);
(键盘上的2就在3旁边)
很多客户端错误可以应为导入新类型而获得预防。真的,在防范“不值得拥有的代码上”,系统类型是你的主要同盟国。
既然这样,让我们导入简单的外覆类型来区分天数,月份和年份,然后于Date构造函数中使用这些类型:
struct Day{
explict Day(int d)
: val(d){ }
int val;
};
struct Month{
explict Month(int m)
:val(m){ }
int val;
};
struct Year{
explict Year(int y):val(y)
{}
int val;
};
class Date{
public:
Date(const Month& m,const Day& d,const Year& y);
};
Date d(30,3,1995); //错误,不正确的类型
Date d(Day(30),Month(3),Year(1995));//错误,不正确的类型
Date d(Month(3),Day(30),Year(1995));//OK,类型正确
令Day,Month,和Year成为成熟且经充分锻炼的classes并封装其内部数据,比简单使用上述的structs好,
但即使structs也已经足够示范:明智而审慎地导入新类型对预防“接口被误用”有神奇疗效。
一旦正确的类型就定位,限制其值有时候是通情达理的。例如一年只有12个有效月份,所以month应该反应这一事实。
办法之一就是利用enum表现月份,但enums不具备我们希望拥有的类型安全性,例如enums可被拿来当一个ints使用
比较安全的解法是预先定义所有有效的months:
class Month{
public:
static Month Jan() {return Month(1);} //函数,返回有效月份,这些是函数而非对象
static Month Feb() {return Month(2);}
static Month Dec() {return Month(12);}
private:
explict Month(int m); //阻止生成新的月份这是月份专属数据
};
Date d(Month::Mar(),Day(30),Year(1995));
如果“以函数替换对象,表现某个特定月份”让你觉得诡异,或许是因为你忘记了non-local static对象的初始化次序有可能出问题。
预防客户错误的另一个方法是,限制类型内什么事可做,什么事不能做。常见的限制是加上const.
例如条款3曾经说明为什么“以const修饰operator*”的返回类型"可阻止客户因“用户自定义类型”而犯错:
if(a*b=c) //原意其实是要做一次比较动作!
下面是另一个一般性准则“让types容易被正确使用,不容易被误用”的表现形式:
“除非有好理由,否则应该尽量令你的types的行为与内置types一致”。
客户已经知道像int这样的type有些什么行为,所以你应该努力让你的types在合样合理的前提下也有相同表现
避免无端与内置类型不兼容,真正的理由是为了提供行为一致的接口。很少有其他性质比得上“一致性”更能导致“接口容易被正确使用”,
也很少有其他性质比得上“不一致性”更加剧接口的恶化。
任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,因为客户可能会忘记做那件事,如:
导入一个factory函数,它返回一个指针指向Investment* createInvestment();
为避免资源泄露,createInvestment返回的指针最终必须被删除,但那至少开启了两个客户错误机会:没有删除指针,或删除同一个指针超过一次。
如何将createInvestment的返回值存储于一个智能指针如auto_ptr或tr1::shared_ptr内,因而将delete责任推给智能指针。
但万一客户忘记使用智能指针怎么办?许多时候,较佳的接口的设计原则是先发制人,就令factory函数返回一个智能指针:
std::tr1::shared_ptr<Investment> createInvestment();
这便实质上强迫客户将返回值存储于一个tr1::shared_ptr内,几乎消弭了忘记删除底部Investment对象(当它不在被使用时)的可能性。
实际上,返回tr1::shared_ptr让接口的设计者得以阻止一大群客户犯下资源泄露的错误,tr1::shared_ptr允许当智能指针被建立起来时指定一个资源释放函数
(所谓删除器,"delete")绑定于智能指针身上(auto_ptr就没有这个能耐)。
假设class设计者期许哪些“从createInvestment取得Investment*指针“的客户将该指针传递给一个名为getRidOfInvestment的函数,而不是直接在它身上动刀(delete)
这样一个接口又开启通往另一个客户错误的大门,该错误是”企图使用错误的资源析构机制“(也就是拿delete替换getRidOfInvestment).
createInvestment的设计者可以针对此问题先发制人:返回一个“将getRidOfInvestment绑定删除器(delete)”的tr1::shared_ptr.
tr1::shared_ptr提供的某个构造函数接受两个实参:一个是被管理的指针,另一个是引用计数变成0时将被调用的“删除器”,像这样:
std::tr1::shared_ptr<Investment> //企图创建一个null shared_ptr
pInv(0,getRidOfInvestment); //并携带一个自定的删除器。
//此式无法通过编译
这不是有效的C++,tr1::shared_ptr构造函数坚持其第一参数必须是个指针,而0不是指针,是个int。
是的,它可被转换为指针,但在此情况下并不够好,因为tr1::shared_ptr坚持要一个不择不扣的指针。
std::tr1::shared_ptr<Investment>
pInv(static_cast<Investment*>(0),getRidOfInvestment)为删除器
因此,如果要实现createInvestment使它返回一个tr1::shared_ptr并夹带getRidOfInvestment函数作为删除器,代码看起来像这样:
std::tr1::shared_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*> (0),getRidOfInvestment);
retVal=;//令retVal指向正确对象
return retVal;
}
当然,如果被pInv管理的原始指针(raw pointer)可以在建立pInv之前先确定下来,那么“将原始指针传给pInv构造函数”会比“先将pInv初始化为null再对它做
一次赋值操作”为佳。
tr1:;shared_ptr有一个特别好的性质是:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误:所谓的“cross-DLL problem”。
这个问题发生于“对象在动态连接程序库(DLL)中被new创建,却在另一个DLL内被delete销毁”。在许多平台上,这一类“跨DLL之new/delete成对运用”
会导致运行期错误。tr1::shared_ptr没有这个问题,因为它缺省的删除器是来自”tr1::shared_ptr“诞生所在的那个DLL的delete.
如果Stock派生自Investment而createInvestment实现如下:
std::tr1::shared_ptr<Investment> createInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}
返回的那个tr1::shared_ptr可被传递给任何其他DLLS,无需在意"cross-DLLproblem"这个指向stock的tr1::shared_ptr会追踪记录“当Stock的引用次数变成0时该调用那个DLL's delete”
本条款并非特别针对tr1::shared_ptr,而是为了“让接口容易被正确使用,不容易被误用”而设但由于tr1::shared_ptr如此容易消除某些客户错误,值得我们合计其使用成本
最常见的tr1::shared_ptr实现品来自boost,Boost的shared_ptr是原始指针的两倍大,以动态分配内存作为普及用途和“删除器之专属数据”,以virtual形式调用删除器,
并在多线程程序修改引用计数时蒙受线程同步化的额外开销。
总之,它比原始指针大且慢,而且使用辅助动态内存。在许多应用程序中这些额外的执行成本并不显著然而其“降低客户错误的”成效却是每个人都看得到。
假设你为一个用来表现日期的class设计构造函数:
class Date{
public:
Date(int month,int day,int year);
};
但它的客户很容易犯至少两个错误,第一,它们会以错误的次序传递参数:
Date d(30,3,1995);
(键盘上的2就在3旁边)
很多客户端错误可以应为导入新类型而获得预防。真的,在防范“不值得拥有的代码上”,系统类型是你的主要同盟国。
既然这样,让我们导入简单的外覆类型来区分天数,月份和年份,然后于Date构造函数中使用这些类型:
struct Day{
explict Day(int d)
: val(d){ }
int val;
};
struct Month{
explict Month(int m)
:val(m){ }
int val;
};
struct Year{
explict Year(int y):val(y)
{}
int val;
};
class Date{
public:
Date(const Month& m,const Day& d,const Year& y);
};
Date d(30,3,1995); //错误,不正确的类型
Date d(Day(30),Month(3),Year(1995));//错误,不正确的类型
Date d(Month(3),Day(30),Year(1995));//OK,类型正确
令Day,Month,和Year成为成熟且经充分锻炼的classes并封装其内部数据,比简单使用上述的structs好,
但即使structs也已经足够示范:明智而审慎地导入新类型对预防“接口被误用”有神奇疗效。
一旦正确的类型就定位,限制其值有时候是通情达理的。例如一年只有12个有效月份,所以month应该反应这一事实。
办法之一就是利用enum表现月份,但enums不具备我们希望拥有的类型安全性,例如enums可被拿来当一个ints使用
比较安全的解法是预先定义所有有效的months:
class Month{
public:
static Month Jan() {return Month(1);} //函数,返回有效月份,这些是函数而非对象
static Month Feb() {return Month(2);}
static Month Dec() {return Month(12);}
private:
explict Month(int m); //阻止生成新的月份这是月份专属数据
};
Date d(Month::Mar(),Day(30),Year(1995));
如果“以函数替换对象,表现某个特定月份”让你觉得诡异,或许是因为你忘记了non-local static对象的初始化次序有可能出问题。
预防客户错误的另一个方法是,限制类型内什么事可做,什么事不能做。常见的限制是加上const.
例如条款3曾经说明为什么“以const修饰operator*”的返回类型"可阻止客户因“用户自定义类型”而犯错:
if(a*b=c) //原意其实是要做一次比较动作!
下面是另一个一般性准则“让types容易被正确使用,不容易被误用”的表现形式:
“除非有好理由,否则应该尽量令你的types的行为与内置types一致”。
客户已经知道像int这样的type有些什么行为,所以你应该努力让你的types在合样合理的前提下也有相同表现
避免无端与内置类型不兼容,真正的理由是为了提供行为一致的接口。很少有其他性质比得上“一致性”更能导致“接口容易被正确使用”,
也很少有其他性质比得上“不一致性”更加剧接口的恶化。
任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,因为客户可能会忘记做那件事,如:
导入一个factory函数,它返回一个指针指向Investment* createInvestment();
为避免资源泄露,createInvestment返回的指针最终必须被删除,但那至少开启了两个客户错误机会:没有删除指针,或删除同一个指针超过一次。
如何将createInvestment的返回值存储于一个智能指针如auto_ptr或tr1::shared_ptr内,因而将delete责任推给智能指针。
但万一客户忘记使用智能指针怎么办?许多时候,较佳的接口的设计原则是先发制人,就令factory函数返回一个智能指针:
std::tr1::shared_ptr<Investment> createInvestment();
这便实质上强迫客户将返回值存储于一个tr1::shared_ptr内,几乎消弭了忘记删除底部Investment对象(当它不在被使用时)的可能性。
实际上,返回tr1::shared_ptr让接口的设计者得以阻止一大群客户犯下资源泄露的错误,tr1::shared_ptr允许当智能指针被建立起来时指定一个资源释放函数
(所谓删除器,"delete")绑定于智能指针身上(auto_ptr就没有这个能耐)。
假设class设计者期许哪些“从createInvestment取得Investment*指针“的客户将该指针传递给一个名为getRidOfInvestment的函数,而不是直接在它身上动刀(delete)
这样一个接口又开启通往另一个客户错误的大门,该错误是”企图使用错误的资源析构机制“(也就是拿delete替换getRidOfInvestment).
createInvestment的设计者可以针对此问题先发制人:返回一个“将getRidOfInvestment绑定删除器(delete)”的tr1::shared_ptr.
tr1::shared_ptr提供的某个构造函数接受两个实参:一个是被管理的指针,另一个是引用计数变成0时将被调用的“删除器”,像这样:
std::tr1::shared_ptr<Investment> //企图创建一个null shared_ptr
pInv(0,getRidOfInvestment); //并携带一个自定的删除器。
//此式无法通过编译
这不是有效的C++,tr1::shared_ptr构造函数坚持其第一参数必须是个指针,而0不是指针,是个int。
是的,它可被转换为指针,但在此情况下并不够好,因为tr1::shared_ptr坚持要一个不择不扣的指针。
std::tr1::shared_ptr<Investment>
pInv(static_cast<Investment*>(0),getRidOfInvestment)为删除器
因此,如果要实现createInvestment使它返回一个tr1::shared_ptr并夹带getRidOfInvestment函数作为删除器,代码看起来像这样:
std::tr1::shared_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*> (0),getRidOfInvestment);
retVal=;//令retVal指向正确对象
return retVal;
}
当然,如果被pInv管理的原始指针(raw pointer)可以在建立pInv之前先确定下来,那么“将原始指针传给pInv构造函数”会比“先将pInv初始化为null再对它做
一次赋值操作”为佳。
tr1:;shared_ptr有一个特别好的性质是:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误:所谓的“cross-DLL problem”。
这个问题发生于“对象在动态连接程序库(DLL)中被new创建,却在另一个DLL内被delete销毁”。在许多平台上,这一类“跨DLL之new/delete成对运用”
会导致运行期错误。tr1::shared_ptr没有这个问题,因为它缺省的删除器是来自”tr1::shared_ptr“诞生所在的那个DLL的delete.
如果Stock派生自Investment而createInvestment实现如下:
std::tr1::shared_ptr<Investment> createInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}
返回的那个tr1::shared_ptr可被传递给任何其他DLLS,无需在意"cross-DLLproblem"这个指向stock的tr1::shared_ptr会追踪记录“当Stock的引用次数变成0时该调用那个DLL's delete”
本条款并非特别针对tr1::shared_ptr,而是为了“让接口容易被正确使用,不容易被误用”而设但由于tr1::shared_ptr如此容易消除某些客户错误,值得我们合计其使用成本
最常见的tr1::shared_ptr实现品来自boost,Boost的shared_ptr是原始指针的两倍大,以动态分配内存作为普及用途和“删除器之专属数据”,以virtual形式调用删除器,
并在多线程程序修改引用计数时蒙受线程同步化的额外开销。
总之,它比原始指针大且慢,而且使用辅助动态内存。在许多应用程序中这些额外的执行成本并不显著然而其“降低客户错误的”成效却是每个人都看得到。
相关文章推荐
- Effective C++ 学习笔记:让operator=返回*this的引用
- “Effective C++ Third Edition”学习笔记(二)
- Effective C++ 学习笔记(8)
- Effective C++ 学习笔记(15)
- Effective C++ 学习笔记(11)
- Effective C++ 学习笔记:避免public接口出现数据成员
- Effective C++ 学习笔记(3)
- 设计模式C++学习笔记之十四(Iterator迭代器模式)
- Effective C++ 学习笔记(5)
- Effective C++ 学习笔记(14)
- Effective C++ 学习笔记:为含指针变量的类声明一个拷贝构造函数和一个赋值操作符
- Effective C++ 学习笔记(1) : 语言联邦、弱化预编译器、const、初始化
- “Effective C++ Third Edition”学习笔记(四)
- Effective C++ 学习笔记(9)
- Effective C++ 学习笔记(6)
- “Effective C++ Third Edition”学习笔记(一)
- more effective c++学习笔记 ---- 条款31
- “Effective C++ Third Edition”学习笔记(三)
- Effective C++ 学习笔记(12)
- Effective C++ 学习笔记(10)