Effective C++(六)

        七、模板和泛型编程

        (41)、了解隐式接口和编译器多态

          面向对象编程总是以显式接口(explicit interfaces)和运行期多态(runtime polymorphism)解决问题。

          Templates及泛型编程的世界,隐式接口(implicit interfaces)和编译器多态(compile-time polymorphism)移到前头了。

          template<typename T>

          void doProcessing(T& w)

         {

                if(w.size() > 10 && w != someNastWidget)

                {

                       T temp(w);

                       temp.normalize();

                       temp.swap(w);

                }

         }

         w必须支持哪一种接口,系由template中执行于w身上的操作来决定。本来来看w的类型T好像必须支持size,normalize和swap成员函数、copy构造函数(用以建立temp)、不等比较(inequality comparism,用来比较someNasty-Widget)。这一组表达式(对此template而言必须有效编译)便是T必须支持的一组隐式接口(implicit interface)。

         凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template具现化(instantiated),使这些调用得以成功。这样的具现行为发生在编译器,“以不同的template参数具现化function templates”会调用不同的函数,这便是所谓的编译器多态(compile-time polymorphism)。


       通常显式接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成。例如Widget class:

       class Widget {

       public:

           Widget();

           virtual ~Widget();

           virtual std::size_t size() const;

           virtual void normalize();

           void swap(Widget& other);

      }

      隐式接口就完全不同了。它并不基于函数签名式,而是由有效表达式(valid expression)组成。再次看看doProcessing template一开始的条件:

      

          template<typename T>

          void doProcessing(T& w)

         {

                if(w.size() > 10 && w != someNastWidget)

                {

                   ...

             T(w的类型)的隐式接口看来好像有这些约束:

             它必须提供一个名为size的成员函数,该函数返回一个整数值。

             它必须支持一个operator!=函数,用来比较两个T对象。这里我们假设someNastyWidget的类型为T。

             由于操作符重载带来的可能性,这两个约束都不需要满足。是的,T必须支持size成员函数,然而这个函数也可能从base calss继承而得。这个成员函数不需返回一个整数值,甚至不需返还一个数值类型。就此而言,它甚至不需要返回一个定义有operator>的类型!它唯一需要做的是返回一个类型为X的对象,而X对象加上一个int(10的类型)必须能够调用一个operator>。这个operator>不需要非得取得一个类型为X的参数不可,因为它也可以取得类型Y的参数,只要存在一个隐式转换能够将类型X的对象转换为类型Y的对象!operater!=也是同样的道理。

       加诸于template参数身上的隐式接口,就像加诸于class对象身上的显示接口一样真实,而且两者都在编译期完成检查。

        请记住:

        classes和templates都支持接口(interface)和多态(polymorphism)。

        对classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期。

        对template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。

       


      (42)、了解typename的双重意义

        以下template声明中,class和typename有什么不同?

         template<class T> class Widget;

         template<typename T> class Widget;        

         答案:没有不同。当我们声明template类型参数,class和typename的意义完全相同。

         template<typename C>

         void print2nd(const C& container)

         {

              if(container.size() >= 2)

              {

                     C::const_iterator iter(containter.begin());

                     ++iter;

                     int value = *iter;

                     std::cout << value; 

              }

         }

         iter的类型是C::const_iterator,实际是什么必须取决于template参数C。template内出现的名称如果相依于template参数,称之为从属参数(dependent names)。如果从属名称在class内呈嵌套状,我们称它为嵌套从属名称。C::const_iterator就是这样一个名称。

       print2nd内的另一个local变量value,其类型是int。int是一个并不依赖任何template参数的名称。这样的名称是谓非从属名称(non-dependent names)。

      在我们知道C是什么之前,没有任何办法可以知道C::const_iterator是否为一个类型。而当编译器开始接受template  print2nd时,尚未确知C是什么东西。C++一个规则可以解析(resolve)此一歧义状态:如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非你告诉它是。当然此规则有个例外。

      因此下面的代码才是合法的(C::const_iterator如果是类型):      

        template<typename C>

         void print2nd(const C& container)

         {

              if(container.size() >= 2)

              {

                   typename  C::const_iterator iter(containter.begin());

                   ...

               }

          }

          一般性规则很简单:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename。

        

          “typename必须作为嵌套从属类型名称的前缀词”这样规则的例外是,typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值列)中作为base class修饰符。例如:

         template<typename T>

         class Derived : public Base<T>::Nested {   //base class list中不允许“typename”

         public:

              expilcit Derived(int x)

                : Base<T>::Nested(x)                          //mem. ini. list中不允许“typename”

             {

                  typename Base<T>::Nested temp;    //嵌套从属类型名称

                  ...                                    //既不在base class list中也不在mem. init. list中,作为一个base class修饰符需加                                                          //上typename。

             }                   

             ...

         };

         下面是一个真实程序中代表性例子:

         template<typename IterT>

         void workWithIterator(IterT iter)

         {

                typename std::iterator_traits<IterT>::value_type temp(*iter);

                ...

        }

        请记住:

        声明template参数时,前缀关键字class和typename可互换。

        请使用关键字typename标识嵌套从属类型名称;但不得在base class list(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。

          


        (43)、学习处理模板化基类内的名称

          class CompanyA {

          public:

               ...

               void sendClearText(const std::string& msg);

               void sendEncrypted(const std::string& msg);

               ...

          };          

          class CompanyB {

          public:

               ...

               void sendClearText(const std::string& msg);

               void sendEncrypted(const std::string& msg);

               ...

          }; 

          ...                                                    //针对其他公司设计的classes    

          class MsgInfo { ... }                         //这个class用来保存信息,以备将来产生信息

          template<typename Company>     

          class MsgSender {

          public:

             ...                                     //构造函数、析构函数等等

             void sendClear(const MsgInfo& info)

             {

                    std::string msg;

                    在这儿,根据info产生信息;

                    Company c;

                    c.sendClearText(msg);

             }

             void sendSecret(const MsgInfo& info)      //类似sendClear,唯一不同是这里调用c.sendEncrypted

             { ... }

          }

          下面是加上对日志信息的管理:

           template<typename Company>

           class LoggingMsgSender: public MsgSender<Company> {

            public:

                 ...                                                       //构造函数、析构函数等等

                 void sendClearMsg(const MsgInfo& info)

                 {

                        将“传送前”的信息写至log;

                        senderClear(info);  //调用base class函数;这段代码无法通过编译

                        将“传送后”的信息写至log;

                }

                 ...

           };

           这段代码无法通过编译,问题在于,当编译器遭遇class template LoggingMsgSender定义式时,并不知道它继承什么样的class。

            为了让问题更具现化,假设我们有个class CompanyZ坚持使用加密通讯:

            class CompanyZ {          //这个class不提供sendClearText函数

             public:

                  ...

                  void sendEncrypted(const std::string& msg);

                  ...

            }

            一般性的MsgSender template对CompanyZ并不合适,我们可以针对CompanyZ产生一个MsgSender特化版:

            template<>

            class MsgSender<CompanyZ>        //一个全特化的MsgSender;它和一般template相同,差别只在于它删掉了                                                                      //sendClear。

            class MsgSender<CompanyZ> {

             public:

               ...

               void sendSecret(const MsgInfo& info)

                { ... } 

            };

            现在如果以CompanyZ调用LoggingMsgSender类,由于sendClear函数不存在,将会出错。那就是C++拒绝这个调用的原因:它知道base class template有可能被特化,而那个特化版本可能不提供和一般性template相同的接口。就某种意义而言,当我们从Object Orinted C++跨进Template  C++,继承就不像以前那般畅行无阻了。

           为了重头来过,我们必须有某种办法令C++“不进入templated base classes观察”的行为失效。有三个办法,第一是在base class函数调用动作之前加上“this->”:

            template<typename Company>            

            public:

                 ...                                                       //构造函数、析构函数等等

                 void sendClearMsg(const MsgInfo& info)

                 {

                        将“传送前”的信息写至log;

                        this->senderClear(info);     //成立,假设sendClear将被继承

                        将“传送后”的信息写至log;

                }

                ...

           };

           第二是使用using声明式。           

           template<typename Company>            

            public:

                using MsgSender<Company>::senderClear;  //告诉编译器,请它假设senderClear位于base class内

                 ...                                                       //构造函数、析构函数等等

                 void sendClearMsg(const MsgInfo& info)

                 {

                        将“传送前”的信息写至log;

                        senderClear(info);     //OK,假设sendClear将被继承下来

                        将“传送后”的信息写至log;

                }

                ...

           };

           第三个做法是,明白指出被调用的函数位于base class内:           

           template<typename Company>            

            public:             

                 ...                                                       //构造函数、析构函数等等

                 void sendClearMsg(const MsgInfo& info)

                 {

                        将“传送前”的信息写至log;

                        MsgSender<Company>::senderClear(info);     //OK,假设sendClear将被继承下来

                        将“传送后”的信息写至log;

                }

                ...

           }; 

          但这往往是最不让人满意的一个解法,因为如果被调用的是virtual函数,上述的明确资格修饰(explicit qualification)会关闭“virtual绑定行为”。

          

          从名称可视点(visibility point)的角度出发,上述每一个解法做的事情都相同:对编译器承诺“base class template的任何特化版本都将支持其一般(泛化)版本所提供的接口”。

          根本而言,本条款探讨的是,面对“指涉base class member”之无效reference,编译器的诊断时间,可能发生在早期(当解析derived class template的定义式时),也可能发生在晚期(当那些template被特定之tempalte实参具现化时)。C++的政策是宁愿早诊断,这就是为什么“当base class从template中被具现化时”它假设他对那些base classes的内容毫无所悉的缘故。

         请记住:

         可在dervied class templates内通过“this->”指数base class template内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。


          

         (44)、将与参数无关的代码抽离templates

           使用template可能会导致代码膨胀(code bloat):其二进制带着重复(或几乎重复)的代码、数据,或两者。其结果有可能源码看起来合身而整齐,但目标吗(object code)却不是那么回事。

           template<typename T,std::size_t n>

           class SquareMatrix {

            public:

              ...

              void invert();

              ...

          };

          SquareMatrix<double,5> sm1;

          sm1.invert();

          SquareMatrix<double,10> sm2;

          sm2.invert();

          ...

          这两份代码并非完全相同,这就是tempalte代码膨胀的典型例子。

          下面是对代码的第一次修改:          

           template<typename T>

           class SquareMatrixBase {

            public:

              ...

              void invert(std::size_t maxtrixSize);

              ...

          };          

          template<typename T,std::size_t n>

           class SquareMatrix: private  SquareMatrixBase<T>{

           private:

                 using SquareMatrixBase<T>::invert;  //避免遮掩base版的invert

            public:

                 ...

                void invert()  { this->invert(n); }  //制造一个inline调用,调用base class版的invert,时候说明为什么这儿出                   ...                                                  //现this->

            };

            这样就共享SquareMatrixBase 唯一一个class内的invert。这个函数使用“this->”记号,因为若不这样做,模板化基类内的函数名称会被derived classes掩盖(条款43)。

           看着一切都好,但是有个棘手的问题就是SquareMatrixBase::invert如何操作数据,如何与derived class互动。可能需要指针,指向一块用来放置矩阵数据的内存起始点。这样将添加额外的参数,却得一次一次的告诉SquareMatrixBase相同的信息,这样似乎不好。

         另外一种办法是令SquareMatrixBase存一个指针,指向矩阵数据所在内存。         

      template<typename T>

           class SquareMatrixBase {

            protected:

              SquareMatrixBase(std::size_t n,T* pMem)  //存储矩阵大小和一个指针,指向矩阵数值。

              :size(n),pData(pMem) {  }

               void setDataPtr(T* ptr) { pData = ptr; }       //重新赋值给pData.

              void invert(std::size_t maxtrixSize);

              ...

           private:

              std::size_t size ; //矩阵大小

              T* pData;           //指针,指向矩阵内容

          };          

          这允许derived classes决定内存分配方式。某些实现版本也许会决定将矩阵数据存储在SquareMatrix对象内部:          

  template<typename T,std::size_t n>

           class SquareMatrix: private  SquareMatrixBase<T>{

           public:

              SquareMatrix()                                                //送出矩阵大小和数据指针给base class

                 : SquareMatrixBase<T>(n,data) {  }

               ...

            private:

              T data[n*n];

           };

          这种类型的对象不需要动态分配内存,但对象自身可能非常大。另外一种做法是把每一个矩阵的数据放进heap(也就是通过new来分配内存):         

          template<typename T,std::size_t n>

           class SquareMatrix: private  SquareMatrixBase<T>{

           public:

              SquareMatrix()                                                //将base class的数据指针设为null,

                 : SquareMatrixBase<T>(n,0) ,                      //为矩阵内容分配内存

                  pData(new T[n*n])                                       //将指向该内存的指针存储起来,

                   { this->setDataPtr(pData.get());  }              //然后将它的一个副本交给base class              

               ...

            private:

               boost::scoped_array<T> pData;

           };

           请记住:

           Templates生成多个class和多个函数,所有任何template代码都不该与某个造成膨胀的template参数产生相依关系。

          因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。

          因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码。             



         (45)、运用成员函数模板接受所有兼容类型

           智能指针的转换是一个问题,本条款试着探讨这个问题。

            Templatea和泛型编程(Generic Programming)

            member function templates(常简称member templates),其作用是为class生成函数:

            template<typename T>

            class SmartPtr {

            public:

                template<typename U>                                   //member template

                SmartPtr(cosnt SmartPtr<U>& other);            //为了生成copy构造函数

                ...

            }            

            以上代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr<U>生成一个SmartPtr<T>——因为SmartPtr <T>有个构造函数接受一个SmartPtr<U>参数。创建的对象时同一个template的不同具现体,有时我们称之为泛化(generalize)copy构造函数。泛化(generalize)copy构造函数支持隐式转换。           

           template<typename T>

            class SmartPtr {

            public:

                template<typename U>                                   

                SmartPtr(cosnt SmartPtr<U>& other)           //以other的heldPtr

                  :heldPtr(other.get()) { ... }                            //初始化this的heldPtr

               T* get() const { return heldPtr; }

                 ...

             private:

                  T* heldPtr;             //这个SmartPtr持有的内置(原始)指针

            }           

            上面的类只有“存在某个隐式转换可将一个U*指针转为T*指针”时才能通过编译。

            

            member function templates(成员函数模板)的效用不限于构造函数,它们常扮演另一个角色的支持赋值。例如TR1的shared_ptr支持所有“来自兼容之内置指针、tr1::shared_ptrs、auto_ptrs和tr1::weak_ptrs”的构造行为,以及所有来自上述各物(tr1::weak_ptrs除外)的赋值操作。下面是tr1::shared_ptrs的一份摘录:

           template<class T>

           class shared_ptr  {

           public:

             template<class Y>

                explicit shared_ptr(Y* p);  //构造,来自任何兼容的内置指针

            template<class Y>

                shared_ptr(shared_ptr<Y> const& r);         //或shared_ptr 

             template<class Y>

                shared_ptr(weak_ptr<Y> const& r);         //或weak_ptr

              template<class Y>

                shared_ptr(auto_ptr<Y> const& r);         //或auto_ptr              

             template<class Y>                                                      //赋值,来自任何兼容的shared_ptr或auto_ptr

                shared_ptr&  operator=(shared_ptr<Y> const& r);         

             template<class Y>                                                  

                shared_ptr&  operator=(auto_ptr<Y> const& r);        

            ...         

          }

         member template并不改变语言规则,所有如果你想要控制copy构造的方方面面,你必须同时声明泛化copy构造函数和“正常的”copy构造函数,相同规则也适用于赋值(assignment)操作。看如下tr1::shared_ptr的一份定义摘要:

         template<class T>         

         class shared_ptr  {

         public:

             shared_ptr(shared_ptr const& r);      //copy构造函数

             template<class Y>                             //泛化copy构造函数

             shared_ptr(shared_ptr<Y> const& r);

             shared_ptr& operator=(shared_ptr const& r);   //copy assignment

             template<class Y>                                            //泛化copy assignment

             shared_ptr& operator=(shared_ptr<Y> const& r);

             ...

           };

           请记住:

           请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。

           如果你声明member template用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。  



         (46)、需要类型转换时请为模板定义非成员函数

           条款24讨论过唯有non-member函数才有能力“在所有实参身上实施隐式类型转换”,但条款并以Rational class的operator*函数为例。本条款将Rational和operator*模板化了:
            template<typename T>

            class Rational  {

                  public:

                      Rational(const T& numerator = 0,const T& denominator = 1);  //条款20告诉你为什么参数一以passed 

                      const T numerator() const;                                                        // by reference方式传递

                      const T denominator() const;                      //条款28告诉你为什么返回值以passed by value方式传递

                      ...                                                                 //条款3告诉你为什么它们是const

           };

           template<typename T>

           const Rational<T> operator*(cosnt Rational<T>& lhs,const Rational<T>& rhs)

           { ... }

           现在来看看实例是否可以运行:

           Rational<int> oneHalf(1,2);   //这个例子来自条款24,唯一不同是Rational改为template。

           Rational<int> result = oneHalf*2;  //错误!无法通过编译。

           上述失败给我们的启示是,模板化的Rational内的某些东西似乎和其non-template版本不同。事实的确如此。在条款24内,编译器知道我们尝试调用什么函数(就是接受两个Rationals参数的那个operator*啦),但这里编译器不知道我们想要调用那个函数。取而代之的是,他们试图想出什么函数被名为operator*的template具现化(产生)出来。它们知道它们应该可以具现化某个“名为operator*并接受两个Rational<T>参数”的函数,但完成这一具现化行为,必须先算出T是什么。问题是它们没有这个能耐。

        因为template实参推导过程中并不考虑采纳“通过构造函数而发生的”隐式类型转换。因此由实例中的2(int)无法推导出Rational<Int>,所有实例无法通过编译。

        template class内的friend声明式可以指涉某个特定函数。那意味class Rational<T>可以声明operator*是它的一个friend函数。Class templates并不依赖template实参推导(后者只施行于function templates身上),所有编译器总是能够在class Rational<T>具现化时得知T。因此,令Rational<T> class声明适当的operator*为其friend函数,可简化整个问题:        

           template<typename T>

            class Rational  {

                  public:

                      ...

                      friend const Rational<T> operator*(cosnt Rational<T>& lhs,const Rational<T>& rhs); 

                      //声明operator*函数

           };

           template<typename T>                                                                                          //定义operator*函数

           const Rational<T> operator*(cosnt Rational<T>& lhs,const Rational<T>& rhs)

           { ... }

           现在对operator*的混合式调用可以通过编译了,因为当对象oneHalf声明为一个Rational<int>,class Rational<int>于是被具现化出来,而作为过程的一部分,friend函数operator*(接受Rational<int>参数)也就被自动声明出来。后者身为一个函数而非函数模板(function template),因此编译器可在调用它是使用隐式转换函数(例如Rational的non-explicit构造函数),而这便是混合式调用之所有成功的原因。

         但是虽然这段代码通过编译,却无法连接。

         先来看看Rational内声明的operator*语法。在声明式一般都省略类型,例如Rational<T>写成Rational。

         上面的代码无法连接,因为我们没有提供定义式,最简单可行的办法是将operator*函数本体合并至其声明式内:         

           template<typename T>

            class Rational  {

                  public:

                      ...

                      friend const Rational<T> operator*(cosnt Rational<T>& lhs,const Rational<T>& rhs); 

                      {

                             return Rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());

                             //实现码与条款24同

                     }       

           };

           本来是使用了inline方式来实现friend函数。如果代码比较多可以考虑使用“令friend函数调用辅助函数”的做法。如下:

           template<typename T> class Rational;   //声明Rational template

           template<typename T>

           const Rational<T> doMultiply(cosnt Rational<T>& lhs,cosnt Rational<T>& rhs);

           

            template<typename T>

            class Rational  {

                  public:

                      ...

                      friend const Rational<T> operator*(cosnt Rational<T>& lhs,const Rational<T>& rhs); 

                      {

                             return doMultiply(lhs,rhs); 

                     }       

           };

          请记住:

          当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。

       


         (47)、请使用traits classes表现类型信息

           STL迭代器分类:

           input迭代器:只能向前移动,一次一步,客户只可读取(不能涂写)它们所指的东西,而且只能读取一次。它们模仿指向输入文件的阅读指针(read pointer);C++程序库中的istream_iterators是这一分类的代表。Output迭代器情况类似,但一切只为输出:它们只向前移动,一次一步,客户只可涂写它们所指的东西,而且只能涂写一次。它们模仿指向输出文件的涂写指针(write pointer);ostream_iterators是这一分类的代表。这是威力最小的两个迭代器分类。由于这两类都只能向前移动,而且只能读或写其所指物最多一次,所有它们之适合“一次性操作算法”(one-pass-algorithns)。

         forward迭代器可以做前述两种分类所能做的每一件事,而且可以读或写其所指物一次以上。这使得它们可施行于多次性操作算法(mulit-pass-algorithns)。slist迭代器就是这类迭代器。

        Bidirectional迭代器除了可以向其移动,还可以向后移动。list迭代器就是这类迭代器,set,multiset,map和multimap的迭代器也都是这一分类。

       random access迭代器比Bidirectional迭代器更多的功能是可以执行“迭代器算术”,也就是可以在常量时间内向前或向后跳跃任意距离。vector,deque和string提供的迭代器就是这一类。

       C++标准程序库提供的各种迭代器结构如下:

       struct input_iterator_tag {};

       struct output_iterator_tag {};

       struct forward_iterator_tag:public input_iterator_tag {};

       struct bidirectional_iterator_tag:public forward_iterator_tag {};

       struct random_access_iterator_tag:public bidirectional_iterator_tag {};

       现在看STL的adance的实现:

       template<tyoename IterT,typename DistT>

       void advance(IterT& iter,DistT d)

       {

          if(iter is a random access iterator)

          {

               iter += d;       //针对random access迭代器使用迭代器算术运算

         }

         else

         {

              if(d>=0) { while(d--) ++iter; }      //针对其他迭代器分类反复调用++或--

              else { while(d++)  --iter; }

         }

      }

      Traits并不是C++关键字或一个预先定义好的构件;它们是一种技术,也是一个C++程序员共同遵守的协议。这个技术的要求之一是,它对内置(built-in)类型和用户自定义类型(user-defined)的表现必须一样好。标准技术是把它放进一个template及其一或多个特化版本中。这样的templates在标准程序库中有若干 个,其中针对迭代器被命名为iterator_traits。

       template<typename IterT>  //template,用来处理迭代器分类的相关信息

       struct iterator_traits;

       iterator_traits是个struct,习惯上成为trait classes。

       iterator_traits的运作方式是,针对每一个类型IterT,在struct iterator_traits<IterT>内一定声明某个typedef名为iterator_category。这个typedef用来确认IterT的迭代器分类。

       例如,deque的迭代器实现如下:

       template<...>

       class deque {

       public:

             class iterator {

              public:

                  typedef random_access_iterator_tag iterator_category;

                   ...

            };

            ...

       };

        list迭代器如下:        

        template<...>

       class list {

       public:

             class iterator {

              public:

                  typedef bidirectional_iterator_tag iterator_category;

                   ...

            };

            ...

       };

       iterator_traits实现如下:

       template<typename IterT>

       struct iterator_traits {

             typedef typename IterT::iterator_category iterator_category;

             ...

      };

      针对指针类型需要偏特化:      

    template<typename IterT>

       struct iterator_traits<IterT*> {        //template偏特化针对内置指针

             typedef typename randon_access_iteraotr iterator_category;

             ...

      };

      定义一个traits class的步骤如下:

      确认若干你希望将来可取得的类型的相关信息。例如对迭代器而言,我们希望将来可取的其分类(category)。

      为该信息选择一个名词(例如iteraotor_category)

      提供一个template和一组特化版本(例如iterator_traits),内含你希望支持的类型相关信息。

       

       现在advance实践先前的伪码(pseudocode):

       

       template<tyoename IterT,typename DistT>

       void advance(IterT& iter,DistT d)

       {

            if(typeid(typename std::iterator_traits<IterT>::iterator_category)== typeid(std::random_access_iterator_tag))

             ...

       }

       现在有个根本的问题需要考虑,IterT类型在编译期间获知,iterator_traits<IterT>::iterator_category可以在编译期间确定。但是if语句却是在运行期才会核定。将编译期间做的事情延迟到运行期才做,不仅浪费时间,也造成可执行文件膨胀。

       我们可以使用重载来替代if表达式。

        tempalte<typename IterT,typename DistT>

        void doAdvance(IterT& iter,DistT d,std::random_access_iterator_tag) //这份实现用于random access迭代器

        {

                iter += d;

        }

        tempalte<typename IterT,typename DistT>

        void doAdvance(IterT& iter,DistT d,std::bidirectional_iterator_tag) //这份实现用于rbidirectional迭代器

        {               

              if(d>=0) { while(d--) ++iter; }     

              else { while(d++)  --iter; }

        } 

       tempalte<typename IterT,typename DistT>

        void doAdvance(IterT& iter,DistT d,std::input_iterator_tag) //这份实现用于input迭代器

        {               

              if(d<0) { throw std::out_of_range("Negative distance"); }     

              else { while(d++)  --iter; }

        }

        doAdvance的input_iterator_tag版本也能够出来forward迭代器。这是public继承带来的部分好处。

        现在可以实现advance的重载调用:         

        tempalte<typename IterT,typename DistT>

        void doAdvance(IterT& iter,DistT d) 

        {               

              doAdvance(    //调用doAdvance版本

                     iter,d,       //对iter之迭代器分类而言必须是适当的

                     typename std::iterator_traits<IterT>::iterator_category()

               );

         }

         现在可以总结如何使用一个traits class了:

         建立一组重载函数(身份像劳工)或函数模板(例如doAdvance),彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受之traits信息相应和。

         建立一个控制函数(身份像工头)或函数模板(例如advance),它调用上述那些“劳工函数”并传递traits class所提供的信息。

        请记住:

           Traits classes使得“类型相关信息”在编译期可用。它们以templates和“templates特化”完成实现。

           整合重载技术(overloading)后,traits classes有可能在编译期对类型执行if...else测试。



        (48)、认识template元编程

           所谓template metaprogram(模板元程序)是以C++写成、执行于C++编译器内的程序。一旦TMP程序结束执行,其输出,也就是从template具现出来的若干C++源码,便会一如往常地被编译。

         TMP让某些事情更容易。由于TMP执行于C++编译期,因此可将工作从运行期转移到编译期。上一条款的traits解法就是TMP。

         st::list<int>::iterator iter;

         ...

         advance(iter,10);  //移动iter向前走10个元素,运行期advance无法通过编译

         

 template<tyoename IterT,typename DistT>

       void advance(IterT& iter,DistT d)

       {

          if(typeid(typename std::iterator_traits<IterT>::iterator_category)== typeid(std::random_access_iterator_tag))

          {

               iter += d;       //错误!

         }

         else

         {

              if(d>=0) { while(d--) ++iter; }      //针对其他迭代器分类反复调用++或--

              else { while(d++)  --iter; }

         }

      }

       typeid这行会因为list<int>::iterators而失败,但编译器必须确保所有源码都有效,纵使是不会执行起来的代码!而当iter不上random acess迭代器时"iter += d"无效。

       TMP强大到足以计算任何事物。

       TMP是个“函数式语言”(functional language)。TMP循环一般是通过递归实现,TMP循环并不涉及递归函数调用,而是涉及“递归模板具现化”(recursive template instantiation)。

      下面看看TMP如何计算阶乘(factorial):

       template<unsigned n>                                      //一般情况:Factorial<n>的值是n乘以Factorial<n-1>的值。

       struct Factorial  {

          enum { value = n * Factorial<n-1>::value };

      };

       template<>                        //特殊情况

       struct Factorial<0> {         //Factorial<0>的值是1

         enum { value = 1 };

       };

       使用Factorial如下:

       int main()

       {

              std::cout << Factorial<5>::value;              //印出120

              std::cout <<Factorial<10>::value;             //印出3628800

      }

       TMP值得学习。可以看下面三个例子:

       确保量度单位正确。

       优化矩阵计算。

       可以生成客户定制之设计模式(custom design pattern)实现品。

       请记住:

       Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。

        TMP可被用来生成“基于政策选择组合”(base on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值