C/C++编程:请使用traits classes表现类型信息

1060 篇文章 295 订阅

STL主要由”用以表现容器、迭代器和算法“的模板构成;但也覆盖若干工具性模板,比如一个叫做advance的可以用来将某个迭代器移动某个给定距离:

template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d);  // d < 0时后移

那应该怎么实现呢?

template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d){
	if(iter is random access iterator){
		iter += d;
	}else{
		if(d > = 0) { while(d--) ++iter;}
		else {while(d++) --iter;}
	}
}

先判断iter是否为随机存取迭代器。也就是说我们需要取得类型的某些信息。这就是traits引入的原因:它们允许你在编译期取得某些类型信息。

Traits并不是C++关键字或者一个预先定义好的构建,它们是一种技术,也是一个c++程序员共同遵守的协议。这个技术的要求之一是,它对内置类型和用户自定义连续性的表现必须一样好。举个例子,如果上面advance收到的实参是一个指针(比如const char *)或者一个int,上面advance还是必须生效。这意味着traits技术也必须能够施行于内置类型比如指针身上。

"traits必须能够施行于内置类型"意味"类型内的嵌套信息"这种东西出局了。因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。标准技术是把它放进一个模板以及一个或者多个特化模板中。这样的模板在标准库中有很多个,其中针对迭代器的被命名为iterator_traits:

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

如你所见,iterator_traits是个struct。是的,习惯上traits总是被实现为struct,但它们却又往往被称为traits classes

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

  • 首先它要求每一个“用户自定义的迭代器类型”必须嵌套一个typedef,名为iterator_category,用来确认适当的tag struct。比如:
template<...>  //略而未写的迭代器参数
class deque{
public:
	class iterator{
	public:
		typedef random_assess_iterator_tag iterator_category;
	};
}

template<...>  //略而未写的迭代器参数
class list{
public:
	class iterator{
	public:
		typedef bidrectional_iterator_tag iterator_category;
	};
}

对于iterator_traits

//  IterT::iterator_category用来表现”IterT说它自己是什么“
template<typename IterT>
struct iterator_traits{
	typedef typename IterT::iterator_category iterator_category;
};

这对自定义类型行得通,但是对指针(也是一种迭代器)行不通,因为指针不可能嵌套typedef。iterator_traits的第二部分如下,专门用来对付指针

  • 为了指针指针迭代器,iterator_traits特别针对指向类型提供一个偏特化版本。由于指针的行径与random assess迭代器类似,所以:
template<typename IterT>  
struct iterator_traits<IterT *>{   //针对内置指针偏特化
	typedef random_access_iterator_tag  iterator_category;
};

总结:如何设计并实现一个traits class

  • 确认若干你希望将来可以取得的类型相关信息。比如对迭代器而言,我们希望将来可以取得的其分类(category)
  • 为信息选择一个名称(比如iterator_category)
  • 提供一个模板和一种特化版本,内含你希望支持的类型相关信息

现在有了iterator_traits,试着实现advance了:

template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d){
	if(typeid(typename std::iterator_traits<IterT>::iterator_category == typeid(std::random_access_iterator_tag ))){
	}

但是上面代码还是有问题:IterT类型在编译期间获得,所以std::iterator_traits< IterT>::iterator_category 也可以在编译期间确定。但是if语句却在运行期才会确定。

那什么解决呢? 重载:当你重载某个函数f,你就必须详细说明各个重载件的类型。当你调用f,编译期会自动根据传过来的实参选择最适当的重载

template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag ){
	iter += d;
}

template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::bidrectional_iterator_tag ){
	if(d > = 0) { while(d--) ++iter;}
		else {while(d++) --iter;}
}


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

有了这些重载版本,advance需要做的只是调用它们并额外传递一个对象,后者必须带有适当的迭代器分类。于是编译期运用重载机制解析调用适当的代码:

template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d){
	doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}

总结:

  • traits类使”类型相关信息“在编译期可用。它们以模板和模板特化完成实现
  • 整合重载技术后,traits类有可能在编译期对类型执行if-else测试

STL迭代器分类:

  • 输入迭代器:只能向前移动,一步一次,客户只可以读取(不能改写)它们所指的东西,而且只能读一次。它们模仿指向输入文件的read pointer。常见例子 istream_iterators
  • 输出迭代器:只能向前移动,一步一次,客户只可以改写(不能读取)它们所指的东西,而且只能写一次。它们模仿指向输入文件的write pointer。常见例子 ostream_iterators

输入迭代器和输出迭代器只能向前移动,而且最多只能读/写一次,所以它们只适合”一次性操作算法“

  • 前向迭代器:只能向前移动,,可以读写它们所指的东西一次以上。适合”多次性操作算法“
  • 双向迭代器:可以前后移动
  • 随机访问迭代器:可以随机跳转任意距离

对于这5中分类,C++标准库提供了专属的tag struct加以确认:

struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidrectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidrectional_iterator_tag {};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值