做者:LogM編程
本文原載於 https://segmentfault.com/u/logm/articles,不容許轉載~segmentfault
//不用模板的寫法 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的具現化,使函數調用得以成功,具現化發生在編譯期。這稱爲"編譯期多態"。
//第一重意義 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是類型 ... }
//基類 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則不會破壞。
//模板類 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無關,但它被編譯器生成了兩份,形成重複。
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); //創建一個泛化拷貝構造函數,來解決上面的問題 ... } //固然,對於賦值函數也能夠這麼操做
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