目次
条款17:让接口容易被精确使用,不易被误用(Make interfaces easy to use correctly and hard to use incorrectly)
限制类型和值
- class Date {
- public:
- Date(int month, int day, int year); //可能月日年顺序错,可能传递无效的月份或日期
- ...
- };
复制代码 可使用类型系统(type system)规避以上错误,即引入外覆类型(wrapper type)区别年代日:- struct Day {
- explicite Day(int d)
- : val(d) { }
- int val;
- }
- struct Month {
- explicite Month(int m)
- : val(m) { }
- int val;
- }
- struct Year{
- explicite Year(int y)
- : val(y) { }
- int val;
- }
- class Date {
- public:
- Date(const Month& m, const Day& d, const Year& y); //可能月日年顺序错,可能传递无效的月份或日期
- ...
- };
- Date d(Month(3), Day(30), Year(1995)); //可有效防止接口误用
复制代码 保证了类型精确之后,需要保证输入的值有用:- 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));
复制代码 规定能做和不能做的事
- if ( a * b = c) ... //以const修饰操作符*,使其不能被赋值
复制代码 提供举动一致的接口
为了避免忘记删除或者重复删除指针,可令工厂函数直接返回智能指针:- Investment* createInvestment(); //用户可能忘记删除或者重复删除指针
- std::tr1::shared_ptr<Investment> createInvestment();
复制代码 若盼望用自定义的getRidOfInvestment,则需要避免误用delete,可思量将getRidOfInvestment绑定为删除器(deleter):
删除器在引用次数为0时调用,故可创建一个null shared_ptr
- std::tr1::shared_ptr<Investment> createInvestment()
- {
- std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),
- getRidOfInvestment); //创建一个null shared_ptr
- retVal = ... ; //令retVal指向目标对象
- return retVal;
- }
复制代码 若pInv管理的原始指针能在pInv创立之前确定下来,则将原始指针直接传递给pInv的构造函数更好
tr1::shared_ptr会主动使用每个指针专属的删除器,从而无须担心cross-DLL problem:
cross-DLL problem:对象在动态毗连程序库(DLL)中被new创建,但在另一个DLL内被delete销毁
- //返回的tr1::shared_ptr可能被传递给任何其他DLL
- //其会追踪记录从而在引用次数为0时调用那个DLL的delete
- std::tr1:;shared_ptr<Investment> createInvestment()
- {
- return std::tr1::shared_ptr<Investment>(new Stock);
- }
复制代码 Boost的tr1::shared_ptr特点:
- 是原始指针的两倍大
- 以动态分配内存作为簿记用途和删除器的专属数据
- 以virtual形式调用删除器
- 在多线程程序修改引用次数时有线程同步化(thread synchronization)的额外开销
Tips:
- 好的接口不易被误用
- 促进精确使用的方法包括接口一致性和与内置类型的举动兼容
- 阻止误用的办法包括建立新类型、限制类型上的操纵、束缚对象值、消除客户的资源管理责任
- tr1::shared_ptr支持定制型删除器(custom deleter),这可以防范DLL题目,可被用来主动解除互斥锁(mutexes)等
条款19:设计class犹如设计type(Treat class design as type design)
定义一个新class时也就定义了一个新type。设计高效的类需要思量以下题目:
- 新type的对象应如何创建和销毁(第8章))
- 影响构造函数和析构函数、内存分配函数和开释函数(operator new,operator new [],operator delete,operator delete [])
- 对象的初始化和赋值应有什么差别(条款4)
- 新type的对象如果被pass-by-value意味着什么
- 由copy构造函数定义pass-by-value如何实现
- 什么是新type的合法值
- 有用的数值集决定了类必须维护的约束条件(invariants),
- 进而决定了成员函数(特别是构造函数、析构函数、setter函数)的错误查抄
- 还影响函数抛出的非常和少少使用的函数非常明细列(exception specifications)
- 新type需要共同某个继承图系(inheritance graph)吗
- 继承既有的类,则受那些类束缚,尤其要思量那些类的函数是否为虚函数
- 被其他类继承,则影响析构函数等是否为virtual
- 新type需要什么样的转换
- 若允许类型T1隐式转换为类型T2,可可思量:
- 在T1类内写类型转换函数(operator T2)
- 在T2类内些non-explicit-one-argument(可被单一实参调用)的构造函数
- 若只允许explicit构造函数存在,就得写专门执行转换的函数,且没有类型转换操纵符(type conversion operators)或non-explicit-one-argument构造函数
- 什么样的操纵符和函数对于此新type合理
- 什么样的标准函数应驳回
- 谁改取用新type的成员
- 影响public、private、protected的选择
- 影响友元类、友元函数、及其嵌套的设计
- 什么是新type的未声明接口(undeclared interface)
- 新type有多么一般化
- 若要定义整个type家族,则应该定义新的class template
- 是否真的需要新type
- 若定义新的派生类就足够,则大概定义non-member函数或templates更好
Tips:
- Class设计就是type设计,需要思量以上所有题目
条款20:宁以pass-by-reference-to-const更换pass-by-value(Prefer pass-by-reference-to-cons to pass-by-value)
避免构造和析构
- class Person {
- public:
- Person();
- virtual ~Person();
- ...
- private:
- std::string name;
- std::string address;
- };
- class Student: public Person {
- public:
- Student();
- ~Student();
- ...
- private:
- std::string schoolName;
- std::string schoolAddress;
- };
- bool validateStudent(Student s); //会调用六次构造函数和六次析构函数
- bool validateStudent(const Student& s); //效率提升很多
复制代码 上述代码validateStudent函数中pass-by-value会调用六次构造函数和六次析构函数:
- Student构造+Person构造+Student的2个string+Person的2个string
- 析构同理
使用pass-by-reference可避免频繁构造和析构
避免对象切割
对象切割(slicing):派生类以值传递并被视为基类对象时,回调用基类的构造函数,而派生类的成分全无
[code]class Window {public: ... std::string name() const; //返回窗口名称 virtual void display() const; //显示窗口和其内容};class WindowWithScrollBars: public Window {public: ... virtual void display() const;};void printNameAndDisply(Window w){ std::cout |