《Effective C++》第三版-4. 设计与声明(Design and Declarations) ...

打印 上一主题 下一主题

主题 913|帖子 913|积分 2739

目次

条款17:让接口容易被精确使用,不易被误用(Make interfaces easy to use correctly and hard to use incorrectly)

限制类型和值
  1. class Date {
  2. public:
  3.         Date(int month, int day, int year);  //可能月日年顺序错,可能传递无效的月份或日期
  4.         ...
  5. };
复制代码
可使用类型系统(type system)规避以上错误,即引入外覆类型(wrapper type)区别年代日:
  1. struct Day {
  2. explicite Day(int d)
  3.         : val(d) { }
  4. int val;
  5. }
  6. struct Month {
  7. explicite Month(int m)
  8.         : val(m) { }
  9. int val;
  10. }
  11. struct Year{
  12. explicite Year(int y)
  13.         : val(y) { }
  14. int val;
  15. }
  16. class Date {
  17. public:
  18.         Date(const Month& m, const Day& d, const Year& y);  //可能月日年顺序错,可能传递无效的月份或日期
  19.         ...
  20. };
  21. Date d(Month(3), Day(30), Year(1995));  //可有效防止接口误用
复制代码
保证了类型精确之后,需要保证输入的值有用:
  1. class Month {
  2. public:
  3.         static Month Jan() { return Month(1); }
  4.         static Month Feb() { return Month(2); }
  5.         ...
  6.         static Month Dec() { return Month(12); }
  7.         ...
  8. private:
  9.         explicit Month(int m);
  10.         ...
  11. };
  12. Date d(Month::Mar(), Day(30), Year(1995));
复制代码
规定能做和不能做的事
  1. if ( a * b = c) ...  //以const修饰操作符*,使其不能被赋值
复制代码
提供举动一致的接口

为了避免忘记删除或者重复删除指针,可令工厂函数直接返回智能指针:
  1. Investment* createInvestment(); //用户可能忘记删除或者重复删除指针
  2. std::tr1::shared_ptr<Investment> createInvestment();
复制代码
若盼望用自定义的getRidOfInvestment,则需要避免误用delete,可思量将getRidOfInvestment绑定为删除器(deleter):
删除器在引用次数为0时调用,故可创建一个null shared_ptr
  1. std::tr1::shared_ptr<Investment> createInvestment()
  2. {
  3.         std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),
  4.                                                                                                                                                                         getRidOfInvestment);  //创建一个null shared_ptr
  5.         retVal = ... ;  //令retVal指向目标对象
  6.         return retVal;
  7. }
复制代码
若pInv管理的原始指针能在pInv创立之前确定下来,则将原始指针直接传递给pInv的构造函数更好
tr1::shared_ptr会主动使用每个指针专属的删除器,从而无须担心cross-DLL problem:
cross-DLL problem:对象在动态毗连程序库(DLL)中被new创建,但在另一个DLL内被delete销毁
  1. //返回的tr1::shared_ptr可能被传递给任何其他DLL
  2. //其会追踪记录从而在引用次数为0时调用那个DLL的delete
  3. std::tr1:;shared_ptr<Investment> createInvestment()
  4. {
  5.         return std::tr1::shared_ptr<Investment>(new Stock);
  6. }
复制代码
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合理

    • 决定需要声明哪些函数,其中哪些是成员函数

  • 什么样的标准函数应驳回

    • 这些必须声明为private

  • 谁改取用新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)

避免构造和析构
  1. class Person {
  2. public:
  3.         Person();
  4.         virtual ~Person();
  5.         ...
  6. private:
  7.         std::string name;
  8.         std::string address;
  9. };
  10. class Student: public Person {
  11. public:
  12.         Student();
  13.         ~Student();
  14.         ...
  15. private:
  16.         std::string schoolName;
  17.         std::string schoolAddress;
  18. };
  19. bool validateStudent(Student s);  //会调用六次构造函数和六次析构函数
  20. 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
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

美食家大橙子

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表