【讀書筆記】Effective C++(07)模板與泛型

做者:LogM編程

本文原載於 https://segmentfault.com/u/logm/articles,不容許轉載~segmentfault

7. 模板與泛型

  • 7.1 條款41:瞭解隱式接口和編譯期多態

    • //不用模板的寫法
      class Widget {
          Widget();
          virtual ~Widget();
          virtual std::size_t size() const;
          virtual void normalize();
          void swap(Widget& other);
          ...
      };
      
      void doProcess(Widget& w) {
          if (w.size() > 10; && w!= ...) {
              Widget temp(w);
              temp.normalize();
              temp.swap(w);
          }
      }
      //w支持的接口是類型Widget決定的,這稱爲"顯式接口"。
      //Widget類裏面的virtual函數是在運行期肯定具體調用哪一個函數,這稱爲"運行期多態"。
    • //使用模板的寫法
      template<typename T>
      void doProcessing(T& w) {
          if (w.size() > 10 && w != ...) {
              T temp(w);
              temp.normalize();
              temp.swap(w);
          }
      }
      //w支持的接口,是由w所參與執行的操做所決定的,好比例子中的w須要支持size()、normalize()、swap()、拷貝構造、不等比較。這稱爲"隱式接口"。
      //w所參與執行的操做,都有可能致使template的具現化,使函數調用得以成功,具現化發生在編譯期。這稱爲"編譯期多態"。
  • 7.2 條款42:瞭解typename的雙重意義

    • //第一重意義
      template<class T> class Widget;
      template<typename T> class Widget;
      //上面兩句話效果徹底同樣
    • //第二重意義
      //考慮一個例子
      template<typename C>
      void print2nd(const C& container) {
          C::const_iterator* x;   //bad,不加typename被假設爲非類型,理由見下面註釋
          ...
      }
      //通常,咱們認爲C::const_iterator指的是某種類型,可是存在一種逗比狀況:
      //C是一個類,const_iterator是這個類的int型的成員變量,x是一個int型的變量,那麼上面一句話就變成了兩個int的相乘。
      //正由於有這種歧義狀況的存在,C++假設不加typename的"嵌套從屬名稱"是非類型。
      
      //應該這麼寫
      template<typename C>
      void print2nd(const C& container) {
          typename C::const_iterator* x;   //ok,告訴編譯器,C::const_iterator是類型
          ...
      }
  • 7.3 條款43:學習處理模板化基類內的名稱

    • //基類
      template<typename T>
      class MsgSender {
      public:
          ...
          void sendClear(const MsgInfo& info);
          ...
      };
      //派生類
      template<typename T>
      class LoggingMsgSender : public MsgSender<T> {
      public:
          void sendClearMsg(const MsgInfo& info) {
              sendClear(info);   //bad,理由見下方註釋
          }
      }
      //編譯器遇到LoggingMsgSender類時,不知道要繼承哪一種MsgSender類,因此編譯器不知道sendClear這個函數是MsgSender類裏繼承下來的成員方法,仍是類外面的全局的函數。
      //爲何說不一樣的MsgSender類不必定有sendClear成員方法呢?由於C++容許template的特化,好比我在下面寫了一個特化的類,這個特化的類爲空類,就沒有sendClear成員方法。
      template<>
      class MsgSender<CompanyZ> {  };
      
      //解決這個問題的方法,本質就是告訴編譯器,sendClear函數的來源。具體來講,有三種方法:
      //方法1
      template<typename T>
      class LoggingMsgSender : public MsgSender<T> {
      public:
          void sendClearMsg(const MsgInfo& info) {
              this->sendClear(info);   //ok,告訴編譯器,sendClear函數是類內的成員方法
          }
      }
      //方法2
      template<typename T>
      class LoggingMsgSender : public MsgSender<T> {
      using MsgSender<T>::sendClear; //先聲明,告訴編譯器,若是遇到sendClear函數,則視爲類內的成員方法進行編譯
      public:
          void sendClearMsg(const MsgInfo& info) {
              sendClear(info);   //ok
          }
      }
      //方法3
      template<typename T>
      class LoggingMsgSender : public MsgSender<T> {
      public:
          void sendClearMsg(const MsgInfo& info) {
              MsgSender<T>::sendClear(info);   //ok,告訴編譯器,sendClear函數是類MsgSender<T>內的成員方法
          }
      }
      //方法3不太好的地方是,假如sendClear()是virtual函數,這種寫法會把它的多態性破壞;方法1和方法2則不會破壞。
  • 7.4 條款44:將與參數無關的代碼抽離templates

    • 編譯器對template的處理,其實是對全部可能的template具現出具體代碼
    • //模板類
      template<typename T, std::size_t n>
      class SquareMatrix {
      public:
          ...
          void invert();    //該函數與template無關
      }
      //使用
      SquareMatrix<double, 5> sm1;
      SquareMatrix<double, 10> sm2;
      sm1.invert();
      sm2.invert();
      //這個例子中,invert()函數與template無關,但它被編譯器生成了兩份,形成重複。
    • 做者認爲將與參數無關的代碼抽離templates,能夠避免編譯器產生這類的重複代碼;但我以爲有時候要達到這個目的,會形成代碼可讀性和編寫效率的降低,實際使用時仍是要權衡。
  • 7.5 條款45:運用成員函數模板接受全部兼容類型

    • 假設派生類D繼承於基類B,由B具現化的模板類和由D具現化的模板類,並不能相互轉換。以代碼表述:
    • class B {...};
      class D : public B {...};
      
      template<typename T>
      class SmartPtr {
      public:
          SmartPtr(T* realPtr);
          ...
      }
      
      //使用
      SmartPtr<B> pt1 = SmartPtr<D>(new D);  //bad,SmartPtr<B>與SmartPtr<D>沒有繼承關係來使得他們相互轉換
      
      //解決方法
      template<typename T>
      class SmartPtr {
      public:
          SmartPtr(T* realPtr);
          template<typename U> SmartPtr(const SmartPtr<U>& other);  //創建一個泛化拷貝構造函數,來解決上面的問題
          ...
      }
      //固然,對於賦值函數也能夠這麼操做
  • 7.6 條款46:須要類型轉換時,請爲模板定義非成員函數

    • 這條把條款24擴充到模板類上。
    • template<typename T>
      class Rational {
      public:
          ...
          Rational(const T& numerator, const T& denominator);
          Rational(sonst T& num);  
          const T numerator() const;
          const T denominator() const;
      }
      
      const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs) {
        ...
      }
      //使用
      Rational<int> lhs(1, 9);
      Rational<int> result;
      result = lhs * 2;   //bad,template的推導不考慮隱式類型轉換,編譯器猜不出T是什麼
      result = 2 * lhs;   //bad,template的推導不考慮隱式類型轉換,編譯器猜不出T是什麼
      
      //解決方法
      template<typename T>
      class Rational {
      public:
          ...
          Rational(const T& numerator, const T& denominator);
          Rational(sonst T& num);  
          const T numerator() const;
          const T denominator() const;
      
          friend const Rational operator*(const Rational& lhs, const Rational& rhs) {
              //這裏要把類外面operator*實現的代碼拷貝一份到這裏
              ...
          }  
          //在類內聲明friend函數,使編譯器在類初始化時能夠先具現出:
          //"const Rational<int> operator* (const Rational<int>& lhs, const Rational<int>& rhs)"
      };
      const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs) {
        ...
      }
      //使用
      Rational<int> lhs(1, 9);
      Rational<int> result;
      result = lhs * 2;   //ok,因爲friend函數帶來的具現化,編譯器執行到這裏時,具現化好的函數中,已經有知足須要的了,不須要推導T
      result = 2 * lhs;   //ok,因爲friend函數帶來的具現化,編譯器執行到這裏時,具現化好的函數中,已經有知足須要的了,不須要推導T
  • 7.7 條款47:使用traits classes表現類型信息

    • STL中普遍使用traits classes來標記容器屬於哪一類容器(好比"可隨機訪問容器":vector、deque等)
  • 7.8 條款48:認識template元編程(TMP)

    • 所謂元編程,是執行於編譯器內的程序,C++以template實現元編程。
    • 優勢:a. 完成一些之前不可能完成的任務;b. 將工做從運行期轉移到編譯期(好比以前在運行期才找到的錯誤能夠在編譯期找到)。
    • 缺點:編譯時間變長。
    • TMP不一樣於"正常化的"C++,尚未徹底被C++標準支持,普通用戶能夠暫時不用瞭解。
相關文章
相關標籤/搜索