做者: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