细读《Effective C++》之十三

Chapter 7. Templates and Generic Programming

templates的一个最重要的作用在于让很多原来在运行期的工作提前到编译期间完成。

Item 41:templates和classes一样都是支持interfaces和polymorphics的,而且tempaltes支持implicit interfaces和compile-time polymorphics。

Item 42:typename可以用于标识nested dependent type name,但在base class lists和member initailization list中不可用作base class identifier。

Item 43:模板化基类存在total template specialization情况,有可能导致base classes中的成员函数无法具现,只能通过this->、using declaration或者explicit calling解决。

Item 44:将与参数无关代码抽离templates可在一定程序上解决代码膨胀问题。

Item 45:用member function templates实现的constructors、assignment operations等可接受所有兼容类型,需要注意的是在实现兼容的同时不要忘记inheritance和其它类型间的约束。

Item 46:non-member functions在混合运算的必要我们在Item 24中已经领略,对于模板化的混合运算,不仅仅需要non-member functions,还需要可以implicit conversion的non-member functions,inline friend non-member functions可以。

Item 47:traits classes在编译期类型信息的获取和函数重载使得本来在运行时完成的工作提前至编译期完成,甚至是运行时的错误也提前在编译时被发现。

Item 48:TMP是一种先进的理念和方法,Scott甚至称之为一种language,可见一斑。

Item 45 - 48

条款45:Use member function templates to accept "all compatible types"

普通指针与智能指针的区别在于:

1) 普通指针在资源管理上不及智能指针安全;

2) 普通指针的实现是物理层面的,而智能指针的实现则是逻辑层面的;

3) 普通指针支持implicit conversions,智能指针并不直接支持implicit conversions。

既然智能指针本质上是objects,如果要使用模板类智能指针,并使其支持implicit conversions,就需要为其实现构造模板:

template<typename T>
class  SmartPtr {
public
:
  template
<typename U>                       // member template

  SmartPtr(const SmartPtr<U>& other);        // for a "generalized
  ...                                        // copy constructor"
};

为了使之具有继承特性,可以进行如下修改:

template<typename T>
class  SmartPtr {
public
:
  template
<typename U>

  SmartPtr(
const SmartPtr<U>& other)         // initialize this held ptr
  : heldPtr(other.get()) { ... }             // with other's held ptr
  T* get() const { return  heldPtr; }
  ...

private:                                     // built-in pointer held

  T *heldPtr;                                // by the SmartPtr
};

template
<class T> class
 shared_ptr {
public
:
  template
<class Y>                                     // construct from

    explicit shared_ptr(Y * p);                         // any compatible

  template
<class Y>                                     // weak_ptr, or
    explicit shared_ptr(weak_ptr<Y> const& r);          // auto_ptr

  template
<class Y>
    
explicit shared_ptr(auto_ptr<Y>&  r);

  shared_ptr(shared_ptr 
const& r);                      // copy constructor


  template
<class Y>          // generalized
    shared_ptr(shared_ptr<Y> const& r);                 // copy constructor

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

  template
<class Y>                                     // shared_ptr or
    shared_ptr& operator=(auto_ptr<Y>& r);              // auto_ptr

  template
<class Y>          // generalized
    shared_ptr& operator=(shared_ptr<Y> const& r);      // copy assignment
  ...
};

1) Use member function templates to generate functions that accept all compatible types.Things to Remember

2) If you declare member templates for generalized copy construction or generalized assignment, you'll still need to declare the normal copy constructor and copy assignment operator, too.

条款46:Define non-member functions inside templates when type conversions are desired

在Item 24中,将CRational的operator *实现为non-member function,现在来看其模板化形式:

template < typename T >
class  Rational {
public :
  Rational(
const  T &  numerator  =   0 ,               //  see Item 20 for why params
            const  T &  denominator  =   1 );            //  are now passed by reference
   const  T numerator()  const ;                     //  see Item 28 for why return
   const  T denominator()  const ;                   //  values are still passed by value,
  ...                                            //  Item 3 for why they're const
};

template
< typename T >
const  Rational < T >   operator * ( const  Rational < T >&  lhs,
                            
const  Rational < T >&  rhs)
{ ... }

Rational
< int >  oneHalf( 1 2 );                     //  this example is from Item 24,
                                                
//  except Rational is now a template
Rational < int >  result  =  oneHalf  *   2 ;              //  error! won't compile

如果将operator*声明为Rational<T>的friend function,这样在编译器在调用operator*时将使用implicit conversions将2转换为Rational<int>:template实参推导将oneHalf、2分开考虑:oneHalf类型为Rational<int>,所以T为int,operator*的第二个参数类型被声明为Rational<T>,但传递的2为int,由于template实参推导不考虑implicit conversions,所以2不会在转换为Rational<int>后再推导T类型。

template < typename T >
class  Rational {
public :
  ...
friend                                                    
//  declare operator*
   const  Rational  operator * ( const  Rational &  lhs,            //  function (see
                            const  Rational &  rhs)            //  below for details)
  {  return  doMultiply(lhs, rhs); }                         //  call helper
};

template
< typename T >   class  Rational;                       //  declare Rational template
template < typename T >                                        //  declare
const  Rational < T >  doMultiply( const  Rational < T >&  lhs,       //  helper
                              const  Rational < T >&  rhs);      //  template

template
< typename T >                                        //  define
const  Rational < T >  doMultiply( const  Rational < T >&  lhs,       //  helper
                              const  Rational < T >&  rhs)       //  template in
{                                                          //  header file,
   return  Rational < T > (lhs.numerator()  *  rhs.numerator(),    //  if necessary
                     lhs.denominator()  *  rhs.denominator());
}

Things to Remember以前使用friend只是为了使一个function或者class可以访问其它类中的non-public部分,而此处的friend则是为了支持所有实参的implicit conversions。

When writing a class template that offers functions related to the template that support implicit type conversions on all parameters, define those functions as friends inside the class template.

条款47:Use traits classes for information about types

不知道traits classes会不会成为新的C++0x标准的一部分,即使不会,我相信traits classes的理念也必将深入人心。

先看一例,STL中有一个tools template名为advance,用以将某iterator移动给定距离:

template < typename IterT, typename DistT >         //  move iter d units forward;
void  advance(IterT &  iter, DistT d);             //  if d < 0, move iter backward

 

template
< typename IterT >                 //  template for information about
struct  iterator_traits;                 //  iterator types

template 
<  ...  >                         //  template params elided
class  deque {
public :
  
class  iterator {
  
public :
    typedef random_access_iterator_tag iterator_category;
    ...
  }:
  ...
};

template 
<  ...  >
class  list {
public :
  
class  iterator {
  
public :
    typedef bidirectional_iterator_tag iterator_category;
    ...
  }:
  ...
};

//  the iterator_category for type IterT is whatever IterT says it is;
//  see Item 42 for info on the use of "typedef typename"
template < typename IterT >
struct  iterator_traits {
  typedef typename IterT::iterator_category iterator_category;
  ...
};

template
< typename IterT >                 //  partial template specialization
struct  iterator_traits < IterT *>           //  for built-in pointer types
{
  typedef random_access_iterator_tag iterator_category;
  ...
};

iterator的类型在编译期间即可确定,而if statement的判定将在运行期间进行,为了能够在编译期间确定iterator_category的值,可以使用overloading:我们知道STL的五种iterators除了random access iterator之外,其它iterators均不能随机访问。如果我们能够在编译时获取类型信息,则可根据类型信息进行讨论。traits的要求之一便是对built-in types和user-defined types的表现要一样好:

template < typename IterT, typename DistT >                //  use this impl for
void  doAdvance(IterT &  iter, DistT d,                   //  random access
               std::random_access_iterator_tag)        //  iterators
{
  iter 
+=  d;
}

template
< typename IterT, typename DistT >                //  use this impl for
void  doAdvance(IterT &  iter, DistT d,                   //  bidirectional
               std::bidirectional_iterator_tag)        //  iterators
{
  
if  (d  >=   0 ) {  while  (d -- ++ iter; }
  
else  {  while  (d ++ -- iter;         }
}

template
< typename IterT, typename DistT >                //  use this impl for
void  doAdvance(IterT &  iter, DistT d,                   //  input iterators
               std::input_iterator_tag)
{
  
if  (d  <   0  ) {
     
throw  std::out_of_range( " Negative distance " );
  }
  
while  (d -- ++ iter;
}

template
< typename IterT, typename DistT >
void  advance(IterT &  iter, DistT d)
{
  doAdvance(                                              
//  call the version
    iter, d,                                               //  of doAdvance
    typename                                               //  that is
      std::iterator_traits < IterT > ::iterator_category()     //  appropriate for
  );                                                       //  iter's iterator
}                                                          //  category

Design and implement a traits class:

1) Identify some information about types you'd like to make available (e.g., for iterators, their iterator category).

2) Choose a name to identify that information (e.g., iterator_category).

3) Provide a template and set of specializations (e.g., iterator_traits) that contain the information for the types you want to support.

Use a traits class:

1) Create a set of overloaded "worker" functions or function templates (e.g., doAdvance) that differ in a traits parameter. Implement each function in accord with the traits information passed.

2) Create a "master" function or function template (e.g., advance) that calls the workers, passing information provided by a traits class.

Things to Remember

1) Traits classes make information about types available during compilation. They're implemented using templates and template specializations.

2) In conjunction with overloading, traits classes make it possible to perform compile-time if...else tests on types.

条款48:Be aware of template metaprogramming (TMP)

Scott指出:TMP has two great strengths:

1) It makes some things easy that would otherwise be hard or impossible (Item 47);

2) TMPs execute during C++ compilation.

在本章中,Scott一直在强调TMP是Turing-complete的,他甚至称:TMP's largely a functional language。

//  TMP : declare variables
std::list < int > ::iterator iter;

//  TMP : perform loops
template < unsigned n >                   //  general case: the value of
struct  Factorial {                    //  Factorial<n> is n times the value
                                     
//  of Factorial<n-1>
   enum  { value  =  n  *  Factorial < n - 1 > ::value };
};

template
<>                             //  special case: the value of
struct  Factorial < 0 >  {                 //  Factorial<0> is 1
   enum  { value  =   1  };
};

//  TMP : write and call functions
template < typename IterT, typename DistT >
void  advance(IterT &  iter, DistT d)
{
  ...
}

advance(iter, 
10 );

1) Ensuring dimensional unit correctness. 将运行时错误提前至编译时侦测。TMP的目标:

2) Optimizing matrix operations.

3) Generating custom design pattern implementations.

Things to Remember

1) Template metaprogramming can shift work from runtime to compile-time, thus enabling earlier error detection and higher runtime performance.

2) TMP can be used to generate custom code based on combinations of policy choices, and it can also be used to avoid generating code inappropriate for particular types. 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值