条款 24提到过为什么non-member函数才有能力“在所有实参身上实施隐式类型转换”,本条款接着那个Rational例子来讲,把Rational class模板化
template<typename T>
class Rational{
public:
Rational(const T& numerator=0,const T& denominator=1);
const T numerator() const;
const T denominator() const;
……
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs)
{……};
Rational<int> oneHalf(1,2);
Rational<int> result=oneHalf*2;//错误,无法通过编译
非模板的例子可以通过编译,但是模板化的例子就不行。在*条款**24,编译器直到我们尝试调用什么函数(就是接受两个Rational参数那个operator ),但是这里编译器不知道。编译器试图想什么函数被命名为operato* 的template具体化出来,它们知道自己可以具体化某个operator* 并接受两个Rational参数的函数,但为完成这一具体化行动,必须先算出T是什么。问题是它们没这个能耐。
看一下这个例子,编译器怎么推导T。本例中类型参数分别是Rational和int。operator* 的第一个参数被声明为Rational,传递给operator* 的第一实参(oneHalf)正类型正是Rational,所以T一定是int。operator* 的第二个参数类型被声明为Rational,但传递给 operator* 的第二个实参类型是int,编译器如何推算出T?或许你期望编译器使用Rational的non-explicit构造函数将2转换为Rational,进而推导出T为int,但它不这么做,因为在template实参推导过程中从不将隐式类型转换考虑在内。隐式转换在函数调用过程中的确被使用,但是在能够调用一个函数之前,首先要知道那个函数的存在;为了知道存在,必须先为相关的function template推导出参数类型(然后才可以将适当的函数具体化出来)。但是在template实参推导过程中不考虑通过构造函数发生的隐式类型转换。
现在解决编译器在template实参推导方面遇到的挑战,可以使用template class内的friend函数,因为template class内的friend声明式可以指涉某个特定的函数。也就是说class Rational可以说明operator* 是它的friend函数。class templates并不依赖template实参推导(后者只施行于function templates身上),所以编译器总是能够在class Rational具体化时得知T。所以令Rational class声明适当的operator*为friend函数,可以简化整个问题。
template<typename T>
class Rational{
public:
……
friend const Rational operator*(const Rational& lhs,const Rational& rhs);//声明
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs)//定义
{……};
这时候对operator* 的混合调用可以通过编译了。oneHalf被声明为一个Rational,class Rational被具体化出来,而作为过程的一部分,friend函数operator* (接受Rational参数)也就自动声明出来。后者身为一个函数而非函数模板,因此编译器在调用它的时候使用隐式转换(将int转换为Rational),所以混合调用可以通过编译。虽然通过编译,但是还会有链接问题,这个稍后再谈。先来看一下Rational内声明operator *的语法。
在一个class template内,template名称可被用来作为template和其参数的简略表达方式,所以在Rational内,我们可以把Rational简写为Rational。如果像下面这样写,一样有效
template<typename T>
class Rational{
public:
……
friend const Rational operator*(const Rational<T>& lhs,const Rational<T>& rhs);//声明
};
现在回头看一下刚刚说的链接的问题。虽然编译器直到我们调用的函数是接受Rational的那个operator * ,但是这个函数只有声明,没有定义。我们本来想让此class外部的operator * 提供定义式,但是这样行不通。如果我们自己声明了一个函数(Rational template内的作为),就有责任定义那个函数。如果没有定义,链接器就找不到它。一个最简单的办法就是将operator * 的定义合并到其声明内:
template<typename T>
class Rational{
public:
……
friend const Rational operator*(const Rational& lhs,const Rational& rhs);//声明+定义
{
return Rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());
}
};
这个技术虽然使用了friend,却与传统的friend用途“访问class的non-public成员”不同。为了让类型转换可能发生与所有实参身上,我们需要一个non-member函数(**条款**24);为了让这个函数被自动具体化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是让它成为一个friend。
定义在class内部的函数都是inline函数,包括像operator * 这样的friend函数。为了将inline声明带来的冲击最小化,可以让operator * 调用定义在class外部的辅助函数。
template<typename T> class Rational;//forward decelarion
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs);
template<typename T>
class Rational{
public:
……
friend const Rational operator*(const Rational& lhs,const Rational& rhs);//声明+定义
{
return doMultiply(lhs,rhs);
}
};
许多编译器会强迫你把template定义式放到头文件,所以有时你需要在头文件定义doMultiply
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs)
{
return Rational<T>(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());
}
doMultiply是个template,自然不支持混合乘法,其实也没必要支持。它只是被operator * 调用,operator * 支持了混合乘法。
总结
STL主要由容器、迭代器和算法的templates构成,也包括若干工具性templates,其中有一个advance用来将迭代器移动某个给定距离:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d);//d大于零,向前移动,小于零则向后移动
表面上看,只是iterate+=d的动作,但是迭代器有5中,只有random access(随机访问)迭代器才支持+=操作。其他类型没这么大威力,只有反复++和–才行。
STL源码中关于迭代器的部分可以参考这里。这里也回顾一下这5中迭代器。
这5中分类,C++标准程序库提供专属卷标结构(tag struct)加以确认:
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 {};
在了解了迭代器类型后,我们该去实现advance函数了。实现要高效,对于random access迭代器来说,前进d距离要一步完成,而其他类型则需要反复前进或后退
template<typename Iter, typename DistT>
void advance(IteT& iter,DistT d)
{
if(iter is a random access iterator)
iter+=d;
else
{
if(d>=0)
while(d--) ++iter;
else
while(d++) --iter;
}
}
在上面实现中要判断iter是否为random access迭代器,即要知道IterT类型是否为random access类型。这就需要traits,它允许我们在编译期间获取某些类型信息。traits是一种技术,是C++程序员共同遵守的协议。这个技术要求之一就是,它对内置类型和自定义类型表现的一样好。traits必须能够施行于内置类型,意味着“类型内的嵌套信息”这种东西出局了,因为我们无法将信息嵌套于原值指针内。所以类型的traits信息必须位于类型自身之外。标准技术是把它放进一个template及其一个或多个特化版本中。这样的templates在STL中有若干个,迭代器的为iterator_traits:
template<typename IterT>//用来处理迭代器分类
struct iterator_traits;
虽然iterator_traits是个struct,往往称作traits classes。其运作方式是,针对每一个类型IterT,在struct iterator_traits内声明某个typedef命名为iterator_category,用来确认IterT的迭代器分类。iterator_traits以两个部分实现上述所言。1、它要求用户自定义的迭代器嵌套一个typedef,名为iterator_category,用来确认是哪个卷标结构(tag struct),例如deque和list
template<typename T>
class deque{
public:
class iterator{
public:
typedef random_access_iterator_tag iterator_category;
……
};
……
};
template<typename T>
class list{
public:
class iterator{
public:
typedef bidirectional_iterator_tag iterator_category;
……
};
……
};
template<typename IterT>//IterT的iterator_category就是用来表现IterT说自己是什么
struct iterator_traits{
//typedef typename的使用,见**条款**42
typedef typename IterT::iterator_category iterator_category;
……
};
这样对用户自定义类型行得通,但是对指针行不通,指针也是迭代器,但是指针不能嵌套typedef。下面就是iterator_traits的第2部分了,专门用来支持指针。
为了支持指针迭代器,iterator_traits特别针对类型提供一个偏特化版本(partial template specialization)。
template<typename IterT>
struct iterator_traits<IterT*>//针对内置指针
{
typedef random_access iterator_tag iterator_category;
……
};
现在可以直到实现一个traits class步骤了
现在可以实现一下advance了
template<typename IterT, typename DistT>
void advance(IterT& iter,DisT d)
{
if(typeid(typename std::iterator_traits<IterT>::iterator_category)==
typeid(std::random_access_iterator_tag))
……
}
虽然逻辑是正确,但并非是我们想要的,抛开编译问题(**条款**48再说),还有一个更根本的问题:IterT类型在编译期间获知,所以iterator_traits::iterator_category在编译期间确定。但是if语句却是在运行期间核定。可以在编译期间完成的事情推到运行期间,这不仅浪费时间,还造成执行文件膨胀。
要在编译期间确定,可以使用重载。重载是在编译期间确定的,编译器会找到最匹配的函数来调用
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::random_access_iterator_tag)
{
iter+=d;
}
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::bidirectional_iterator_tag)
{
if(d>=0)
while(d--) ++iter;
else
while(d++) --iter;
}
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, 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(iter,d,typename::std::iterator_traits<IterT>::iterator_category();
}
因为forward_iterator_tag继承自input_iterator_tag,所以input_iterator_tag版本的函数可以处理forward迭代器,这是因为public继承是is-a关系。
现在来总结一下如何使用traits class
Traits广泛应用在STL,除了上面所说的iterator_traits,还有char_traits用来保存字符类型相关信息,numeric_limits用来保存数值类型相关信息。
TR1(**条款**54导入许多新的traits classes用来提供类型信息,例如is_fundamental判断T是否是内置类型,is_array判断T是否为数组,is_base_of
原文:http://blog.csdn.net/kangroger/article/details/44246535