Effective C++ 阅读笔记之Chapter4

前言

大家好,我是雨墨,我深知好记性不如烂笔头的道理,所以在阅读的时候都尽量写读书笔记,方便日后复习,当然笔记并不能代替书籍上的内容。希望我的笔记也能帮助到大家,如果我的笔记有什么问题,欢迎大家给小老弟纠错~

条款十八总结

设计者应该把用户当作白痴,这样才能将问题考虑周到。

  • 设计良好的接口,不容客户使用错误,比如年月日那就专门定义类来实现年月日,同时每个月由专门设计的函数来实现。

  • 让 types 容易被使用,尽量让你的 types 的行为与内置类型保持一致性。

  • 当你写一个工厂函数的时候,一定要留意,你的客户是个傻子,他会忘记最后 delete 这个指针的,因此建议返回智能指针,例如:

    Investment* createInvestment();
    // 改为
    shared_ptr<Investment*> createInvestment();
    
  • 如果你想给指针传递一个函数,而不是对这个指针动用 delete 这把刀子,那也请你显式声明出来:

    shared_ptr<Investment*> createInvestment() {
        shared_ptr<Investment*> retVal(static_cast<Investment*>(0), getRidOfInvestment);	// note!
        retVal = ...;
        return retVal;
    }
    

条款十九总结

设计一个 class 就像设计一个 type 一样,需要考虑许多问题,详见书 p84-p86。

条款二十总结

宁以 pass-by-reference-to-const 替换 pass-by-value,这句话一定要强调一下,下面阐述为什么要这么做:

  • pass-by-value 往往带着昂贵的拷贝构造操作、析构操作,而 pass-by-reference-to-const 没有任何构造函数或析构函数调用,因此没有任何新对象被创建。

  • pass-by-value 会造成对象切割的问题,详见代码

    class Window {
    public:
    	...
    	string name() const;
        virtual void display() const;
    }
    
    class WindowWithScrollBars : public Window {
    public:
    	...
    	virtual void display() const;
    }
    
    void printNameAndDisplay(Window w) {
        cout << w.name();
        w.display();	// 由于是 pass-by-value ,所以调用的永远是 Window::display()
    }
    

    上述传递参数是 pass-by-value ,base class 的 ctor 就会被调用,所以 derived class 的特化性质全部被切割,仅仅留下一个 base class 对象。

  • 窥视 C++ 编译器的底层,reference 往往是以指针实现出来的,所以 pass by reference 通常意味着真正传递的是指针。

  • 尽管当前的 class 可能含有的成员非常小,但是编译器依旧会拒绝将其放入 cache 中,谁知道它将来会有多大,但是如果是 by reference 传递,编译器很乐意将其放入 cache 中。

条款二十一总结

不要试图返回 pointer 或 reference 指向一个 local stack 对象,或是指向 heap-allocated 对象,亦或是返回 reference 或 pointer 指向 local static 对象,这样会给你带来各种各样的麻烦,想象一下,你正在使用 reference 指向一个对象的残骸,或是忘记 delete 一个 heap-allocated 对象从而造成内存泄漏,亦或是带来多个一样结果。这些都在警告你不要使用 pass-by-reference 返回一个原来根本不存在的对象,而应该使用 pass-by-value。

条款二十二总结

将你的成员变量声明为 private !protected 也不行!

原因:

  • 一致性,假设 public 全是接口,这样客户就不用记访问 class 成员需不需要加小括号
  • 使用函数可以对成员变量的处理有更精确的控制
  • 封装!将成员变量隐藏在函数接口背后,为 “所有可能的实现” 提供弹性,封装性与当其内容改变时造成的代码破坏量成反比,例如:将成员变量声明为 private ,造成的代码破坏量是最小的,因此它的封装性更好,如果是声明为 public ,那删除该 public 成员变量,所有使用它的用户代码都会被破坏,封装性极差,而声明为 protected ,你以为就没事了吗?使用它的 derived class 都会被破坏。

条款二十三总结

面向对象的守则:要求对象尽可能被封装,而不是数据和操作数据的函数应该被捆绑在一起。

对于封装的讨论:越多的对象被封装,对用户就越不可见,就能越自由的修改代码而只对少部分用户产生影响,这样弹性也就更大。

那这样才能导致较大的封装性呢?可以以数据的函数被调用的次数来粗略的评估数据的封装性如何。因为 non-member non-friend func 并不能直接访问 private 成员,所以它并不增加能够访问 private 成员的函数的数量。所以使用 non-member non-friend func 可以导致良好的封装性。

在 C++ 中,将这些 non-member non-friend 函数(便利函数)声明与 class 放在同一个 namespace 中,然后再根据这些便利函数的功能划分,定义在不同的头文件中。如果向扩展一组遍历函数,那就在 namespace 中加入函数的声明,再在对应头文件中给出函数的定义即可。

条款二十四总结

当你希望为某个函数的所有参数都进行类型转换(包含 this 所指的隐式参数),你必须将该函数声明为 non-member 。举例说明:

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1);
    int numerator() const;
    int denominator() const;
   	const Rational operator* (const Rational& rhs) const;	// 如果是这样声明,那么后续不能使用 result = 2 * oneHalf	
private:
	...
};
// 但如果是这样,那么就可以使用 reslut = 2 * oneHalf
const Rational operator* (const Rational& lhs, const Rational& rhs) {
    return Rational(lhs.numerator() * rhs.numerator()), 
    				lhs.denominator() * rhs.denominator());	// 注意,这里的 numerator() 你不觉得设计的也很棒吗?
};	

无论何时你可以避免 friend 函数就该避免!

条款二十五总结

  1. 如果缺省实现的 swap 能够为你的 class 或 class template 提供可以接受的效率,那就直接用吧!

  2. 如果你觉得缺省实现的 swap 效率不太行,一般是因为调用缺省实现的 swap 会调用很多次构造函数以及拷贝复制,即你的 class 中或者 template 中使用了某种 pimpl 手法(pointer to implementation),那你就要考虑自己动手实现一个 swap 函数了:

    1. 在 public 中提供一个 swap 成员,这个函数绝不能抛出异常,因为 swap 是异常安全性编程的脊柱!

    2. 在 class 或 template 所在的 namespace 中提供一个 non-member swap ,调用上述的成员 swap,例如:

      namespace WidgetStuff {
          class WidgetStuff {
          public:
              ...
          private:
              int a, b, c;
              std::vector<double> v;
              ...
          };
          ...
          class Widget {
          public:
      		...
               void swap(Widget& other) {	// 这个函数一定不能抛出异常
                  using std::swap;	// 这个一定要!见下方解释
                  swap(pImpl, other.pImpl);
              }
              ...
          private:
              WidgetImpl* pImpl;
          };
          
          //void Swap(Widget& lhs, Widget& rhs) {
            //  lhs.swap(rhs);	// 调用 swap 成员函数
          //}
      }
      
      namespace std {
          template<>
          void swap(Widget& lhs, Widget& rhs) {
              lhs.swap(rhs);
          }
      }
      
    3. 如果你正在编写一个 class (而非 class template),为你的 class 特化 std::swap(见上方代码),并令其调用你的 swap 成员函数。因为 std::swap 是一个 func template , template func 不能被偏特化,如果你实在想要偏特化一个 func template ,作者给出的做法是,提供一个它的重载版本,例如:

      namespace std {
          template<typename T>
          void swap<Widget<T>> (Widget<T>& a,			// error!!!不可以对函数模板进行偏特化
                                 Widget<T>& b)
          { a.swap(b); }	
      }
      
      // 做法:重载
      namespace std {
          template<typename T>
          void swap(Widget<T>& a,
                   	Widget<T>& b)
          { a.swap(b); } 
      }
      

      解释为什么要多家一个 using std::swap ?
      这样做并非多余,因为编译器看到对 swap 的调用,会优先选择调用专属版本的 swap 版本,但如果是没有 T 类型专属的 swap ,编译器就使用 std::swap 。

参考书籍:
《Effective C++》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值