Effective C++笔记(10)—模板与泛型编程(二)

条款46:需要类型转换时请为模板定义非成员函数

考虑之前的例子,对于操作符重载:

class Rational{
public:
    Rational() :x(0){}
    Rational(int x_) :x(x_){}
    Rational &operator*(const Rational& rhs)
    {
        x *= rhs.x;
        return *this;
    }
    int x;
};

int main()
{
    Rational a(2);
    Rational b = a * 2;
    cout << b.x << endl;//ok 4
    //Rational c = 2 * a << endl;//error
    system("pause");
    return 0;
}

在条款24中,说明希望将operator*声明为非成员函数:

class Rational{
public:
    Rational() :x(0){}
    Rational(int x_) :x(x_){}

    int x;
};
const Rational operator*(const Rational &l, const Rational &r)
{
    return Rational(l.x*r.x);
}

int main()
{
    Rational a(2);
    Rational b = a * 2;
    cout << b.x << endl;//ok 4
    Rational c = 3 * a ;//ok
    system("pause");
    return 0;
}

但是,如果我们将上述代码改成template格式,可能出问题:

template <class T>
class Rational{
public:
    Rational() :x(0){}
    Rational(T x_) :x(x_){}

    T x;
};
template <class T>
const Rational<T> operator*(const Rational<T> &l, const Rational<T> &r)
{
    return Rational(l.x*r.x);
}

int main()
{
    Rational<int> a(2);
    Rational<int> b = a * 2;//error
    system("pause");
    return 0;
}

第一个是实参是Rational,通过此推导出T为int
传递的第二个实参类型是int,编译器无法推算出T类型,这里不具备构造函数隐式类型转换的条件。

解决的办法是用友元函数:

template <class T>
class Rational{
public:
    Rational() :x(0){}
    Rational(T x_) :x(x_){}

    T x;

    friend const Rational<T> operator*(const Rational<T> &l, const Rational<T> &r)
    {
        return Rational(l.x*r.x);
    }

};


int main()
{
    Rational<int> a(2);
    Rational<int> b = a * 2;//ok 4
    cout << b.x << endl;
    system("pause");
    return 0;
}

通过第一个实参Rational具现化出对应类,接受Rational参数的friend也被具现化出来,这样就满足了隐式转换的条件了。

但是有个缺陷,那就是在类内定义可能会被编译器内联,如果不想inline,我们可以不在operation*中做事情,改调用外部函数:

template <class T>
const Rational<T>func(const Rational<T> &l, const Rational<T> &r)
{
    return Rational<T>(l.x*r.x);
}

template <class T>
class Rational{
public:
    Rational() :x(0){}
    Rational(T x_) :x(x_){}

    T x;

    friend const Rational<T> operator*(const Rational<T> &l, const Rational<T> &r)
    {
        return func(l, r);
    }

};


int main()
{
    Rational<int> a(2);
    Rational<int> b = 2 * a;//ok 4
    cout << b.x << endl;
    system("pause");
    return 0;
}

条款47:请使用traits classes表现类型信息

看到traits我很亲切,萃取器,早先学习STL源码剖析的时候,对这个还算理解得透彻。

回顾一下STL的迭代器:
迭代器类型(iterator_category)是迭代器五种型别之一:
迭代器被分为5类:

Input :只读 
Output :只写 
Forward :可读写 
Bidirectional:可双向读写 
Random Access:可以随机访问 

通过is-a的关系表示

//继承关系
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 {};

为什么这里都是tag了,事实上,在STL里面只需要一个类型去标记一下即可。在EffectiveC++中,举了advance的例子,advance函数用来执行it+=d操作,但是除了random_access之外,其他迭代器类型并不支持+=,只能执行++或者--操作执行d次。显然,advance函数需要根据迭代器类型进行重载。

看看STL里面的实现:

//1.
template <class _InputIterator, class _Distance>
inline void advance(_InputIterator& __i, _Distance __n) {
  __STL_REQUIRES(_InputIterator, _InputIterator);
  __advance(__i, __n, iterator_category(__i));
}

外部接口advance转调用__advance,这里模板有两个class,一个是迭代器类型,一个是步进distance的类型。

抛开__advance先不看,注意到一个函数iterator_category

//2.
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
iterator_category(const _Iter& __i) { return __iterator_category(__i); }

先不管函数的返回值,外部接口iterator_category调动了__iterator_category

//3.
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category//一整行作为函数返回类型
__iterator_category(const _Iter&)
{
  typedef typename iterator_traits<_Iter>::iterator_category _Category;
  return _Category();//创建一个临时对象 例如int()创建一个临时的int对象。
}

我们看到,Iterator_category通过内部转调用,最终能够返回一个iterator_categor的临时对象,这个临时对象作为__advance的第三个实参。

现在来看:
template <class _Iter> typename iterator_traits<_Iter>::iterator_category
是怎么来的:

//4.
template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

//原生指针的特化版本
template <class _Tp>
struct iterator_traits<_Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};

//const原生指针的特化版本
template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};

原生指针的迭代器类型是支持双向随机访问,因此是random_access_iterator_tag,其他的迭代器例如vector<int>::iterator的类型就在其class中声明了,这里直接使用::访问即可。

到此,先不管__advance的内部实现,我们来看一下整个逻辑:
1.advance接口转调用__advance
2.在__advance中有一个iterator_category(i)参数
3.iterator_category(i)调用了__iterator_category(i)
4.__iterator_category通过i推断出模板类型,并调用iterator_traits获取迭代器的iterator_category型别。
5.返回一个上述型别的临时对象。

最后我们来看看__advance的实现,上文提到,由于不是所有的iterator都支持+=操作,因此这一定是一个重载的过程:

//__advance的重载版本,这里只有类型,没有临时变量(因为不必要)仅仅用来重载
//重载 __advance  input_iterator
template <class _InputIter, class _Distance>
inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) {
  while (__n--) ++__i;
}
//BidirectionalIterator重载版本
template <class _BidirectionalIterator, class _Distance>
inline void __advance(_BidirectionalIterator& __i, _Distance __n, 
                      bidirectional_iterator_tag) {
  __STL_REQUIRES(_BidirectionalIterator, _BidirectionalIterator);
  if (__n >= 0)//可以双向
    while (__n--) ++__i;
  else
    while (__n++) --__i;
}
//RandomAccessIterator重载版本
template <class _RandomAccessIterator, class _Distance>
inline void __advance(_RandomAccessIterator& __i, _Distance __n, 
                      random_access_iterator_tag) {
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  __i += __n;
}

我们看到,第三个参数虽然返回了一个临时变量,但是在重载的版本里面并没有用到,只是通过类型来进行重载。

那么问题来了,Forward版本的__advance去哪了?
原来由于继承关系,当参数无法完全匹配时,会自动传递调用 Input的版本。

最后,上文说到使用traits拿到iterator_category,是这样的访问顺序:

list<>::iterator::iterator_category

可以想见,在STL中应该有这样的代码:

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

条款48:认识template元编程

以前真的没有接触过,模板元编程真的是amazing.
先看一个求阶乘的例子:

template <unsigned n>
struct Factorial{
    enum { value = n*Factorial<n - 1>::value };//enum hack
};
template <>
struct Factorial < 0 > {
    enum { value = 1 };//enum hack
};
int main()
{
    cout << Factorial<5>::value << endl;//120
    system("pause");
    return 0;
}

这个例子就是模板元编程里面的"hello world"了…

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值