1

作者:LogM

本文原载于 https://segmentfault.com/u/logm/articles,不允许转载~

4. 设计与声明

  • 4.1 条款18:让接口不易被误用

    • 简单来说,就是考虑用户可能的误用行为,在代码中规避。比如工厂函数返回值为"智能指针"类型,避免用户使用一般指针管理资源带来的资源泄漏风险;比如将 operator* 的返回类型设为const,避免用户写出"a*b=c"这样的代码。
  • 4.2 条款19:像设计type一样设计class

    • 作者的意思是,设计class要考虑很多细节,尽量使设计出来的class像C++内置类型一样有极大的可用性和鲁棒性。
  • 4.3 条款20:宁以 pass-by-reference-to-const 替换 pass-by-value

    • 原因:a. 参数使用引用传递,不需要构造新对象,比较快;b. 避免对象切割问题。
    • 对象切割(slicing):当派生类对象以 by-value 方式传递参数并被视为基类对象,基类的拷贝构造函数在构造时会把派生类特有的性质抹除。
    • "引用"在编译器底层的实现就是指针(指针的本质是int类型的变量),所以在以下场景,"引用"并不一定比pass-by-value快:a. C++内置类型;b. STL的迭代器(底层实现是指针);c. 函数对象(底层实现是指针)。
  • 4.4 条款21:不要让函数返回值是指向局部变量的引用或指针

    • 原因应该很容易理解:局部变量在函数调用结束后就销毁了,那么这个函数返回的引用和指针指向的内存已经无效了。
  • 4.5 条款22:将成员变量声明为 private

    • 一致性:成员变量为 private,则用户想访问成员变量必须通过成员函数,所以用户就不用试着记住是不是要加括号,因为成员函数都要加括号。
    • 安全性:通过成员函数控制用户对成员变量的读写权限。
    • 封装性:class的版本发生变化,但提供给用户的API还是可以保持不变。
  • 4.6 条款23:宁以 non-menber、non-friend 替换 member 函数

    • 使用场景如下代码所示。作者倾向于non-member、non-friend的理由是:它们无法访问private的成员变量,在封装性上更好,编译时候的依赖程度也低。
    • 作者的说法有一定道理,但我不完全同意作者的观点:

      • a. member函数可以访问private的成员变量,并不意味着用户就可以接触到private的成员变量,你在写代码的时候不让这个member函数访问private的成员变量不就可以了?(此时问题变成了:如何确保写代码的人不在这个函数中滥用private的成员变量)
      • b. 有些情况,使用non-member、non-friend函数会降低代码接口的一致性。作者的解决思路是把non-member、non-friend函数放在和类同一个namespace下,我想了想,这么做一致性还是不如直接写member函数。
    • class WebBrowser {
      public:
          ...
          void clearCache();
          void clearHistory();
          void clearCookies();
          ...
      }
      
      //假如现在要写一个函数clearEverything(),作用是同时清理cache、history、cookies。
      
      //使用member函数的情况
      class WebBrowser {
      public:
          ...
          void clearEverything();
          ...
      }
      void WebBrowser::clearEverything() {
          clearCache();
          clearHistory();
          clearCookies();
      }
      
      //使用non-member函数的情况
      void clearBrower(WebBrowser& wb) {
          wb.clearCache();
          wb.clearHistory();
          wb.clearCookies();
      }
  • 4.7 条款24:若所有参数都需要类型转换,请把这个函数写成 non-member 函数

    • //第一种情况:乘法函数为member函数
      class Rational {
      public:
          ...
          Rational(int numerator, int denominator);
          Rational(int num);  //这个构造函数使得该类支持从int到Rational的类型转换。如果前面加explict则说明不支持隐式类型转换仅支持显式转换,现在没加,支持隐式转换
          const Rational operator* (const Rational& rhs) const;
      }
      
      //使用
      Rational lhs(1, 9);
      Rational result;
      result = lhs * 2;   //ok,2不是Rational类型,但可以发生隐式类型转换
      result = 2 * lhs;   //bad,2不是Rational类型
    • //第二种情况:乘法函数为non-member函数
      class Rational {
      public:
          ...
          Rational(int numerator, int denominator);
          Rational(int num);  //这个构造函数使得该类支持从int到Rational的类型转换
      }
      
      const Rational operator* (const Rational& lhs, const Rational& rhs) {
        ...
      }
      
      //使用
      Rational lhs(1, 9);
      Rational result;
      result = lhs * 2;   //ok,2不是Rational类型,但可以发生隐式类型转换
      result = 2 * lhs;   //ok,2不是Rational类型,但可以发生隐式类型转换
  • 4.8 条款25:考虑写出一个不抛异常的swap函数

    • STL库中std::swap的典型实现如下代码,这个实现比较平淡。对于某些类,写一个模板特化的swap执行效率会更高。作者介绍了怎么自己写std::swap的特化版本,这边就不展开了。
    • namespace std {   //平淡的std::swap实现
          template<typename T>
          void swap(T& a, T& b) {
              T temp(a);
              a = b;
              b = temp;
          }
      }

LogM
85 声望18 粉丝