迭代器概念与traits编程

迭代器概念与traits编程技法

迭代器定义:提供一种方法,使之能够依序巡防某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式

一.迭代器设计思维—STL关键所在

STL中心思想在于将容器与算法分开,彼此独立设计然后用迭代器将两者撮合在一起,算法可以通过迭代器访问容器。

二.迭代器是一种smart pointer

开始先介绍了一个auto_ptr(这个和智能指针好像差不多),auto_ptr可以在离开作用域的时候自动销毁从而避免造成内存泄漏,实现方式就是把指针封装在一个auto_ptr类中,析构函数为delete,迭代器也可以借鉴auto_ptr的设计方式,封装一个相应类型的指针在类中,然后设计相应的函数使这个类表现的像个指针(主要是重载),后面设计了一个用于list的find的迭代器,这个迭代器也是只能给list用,而且单独在外面设计的话暴露了list太多的信息,还不如就在list里面设计,所以我们看到的迭代器都是专门设计在每一种STL容器的里面。

我觉得要理解迭代器很关键的一点就是迭代器的表现像指针,但这种相似是通过封装了指针以及许多函数的模板类实现的。

三.迭代器相应类别

假如设计好了迭代器,但是有一个问题,如何“以迭代器所指对象的型别”作为型别,如果我们定义了一个容器,然后用星号去取迭代器所指内容,得到的是值而不是型别,这里我们用的是模板函数的自动推导,即

template<typename T1, typename T2>
void func_impl(T1 iter, T2 t)
{
    T2 temp;
    // ......
}

template<typename T>
void func(T iter)
{
    func_impl(iter, *iter);
}

func是一个使用迭代器所指型别的接口,这样传参func_impl中的T2就是迭代器所指型别了。

与迭代器有关的型别不只有迭代器所指对象的型别这一种,还有许多其他的无法用这种参数推导的方式获得,所以我们需要更加全面的解法。

四.Traits编程技法——STL源代码门钥

前面我们说过,迭代器有许多相关的类型,其中许多类型是不能用三中类型推导的方法得到的,例如迭代器所指类型作为函数返回类型,所以我们又想了个办法:迭代器声明中内嵌型别,即

template<typename T>
class MyIter{
    typedef T value_type; //这个不就把迭代器指向的类型内嵌了吗
    //......
}

template<typename T>
typename T::value_type//返回值类型
func(I ite)
{
    return *ite;
}

这样就可以直接使用迭代器相关的型别了。

但是还有一个问题,就是不是所有的迭代器都是class type的,如果不是class type,就不能定义内嵌的型别,例如指针就不是class type(但是STL以及整个泛型思维都必须把原生指针作为一种迭代器),这个时候,就要用到偏特化了。

什么是偏特化,就是有一个主模板,然后有其他同名的模板通过直接指定部分模板参数完成偏特化(指定全部参数那叫全特化),具体的偏特化定义与举例可以自行上网查看。

这里我们设计了一个很厉害的东西:iterator_traits,这个类是对迭代器特性的萃取,即把迭代器作为模板类iterator_traits的模板参数,iterator_traits可以萃取出该迭代器的各种特性(注意,这些特性是迭代器自行定义的)。

template<typename T>
struct iterator_traits
{
    typedef typename T::value_type value_type;
}

那这个是如何与偏特化结合解决原生指针的问题呢

template<typename T>
struct iterator_traits<T*>
{
    typedef typename T value_type;
}

或者

template<typename T>
struct iterator_traits<const T*>
{
    typedef typename T value_type;
}

所以对原生指针或指向常数对象的指针我们都需要设计偏特化版本。

即各个容器里的迭代器定义自己的相关类型,然后普通的iterator_traits萃取这些迭代器的特性,两个偏特化的iterator_traits萃取指针相关的特性。

最常用到的迭代器相关型别有五种:value type(迭代器所指对象的类型),difference type(两个迭代器的距离,迭代器的最大容量,总之是个数值型的),pointer(指向迭代器所指之物的指针),reference(迭代器所指之物的引用),iterator category(迭代器的移动特性与施行操作)。

1.value type

迭代器所指对象的类型,直接在迭代器内部定义即可,偏特化也比较简单。

2.difference type

两个迭代器的距离,迭代器的最大容量,总之是个数值型的,也比较简单,偏特化用的是C++内建的ptrdiff_t。

3.reference type

迭代器所指之物的引用

4.pointer type

指向迭代器所指之物的指针

5.iterator_category

前几个都比较简单,这个就有了复杂了。或者说,前几个都跟迭代器的模板参数有关,这个可能有关吧,但是有五个具体的类型供迭代器选择。

这个iterator_category是按照移动特性和施行操作分成五种(这里面input和output最弱,然后逐渐增强):

Input iterator:迭代器所指对象只读

Output iterator:只写

Foward iterator:允许“写入型算法”对迭代器所指区间进行读写

Bidirectional iterator:可双向移动

Random Access iterator:包含所有算术能力,如p+n,p-n,p[n]

我们可以把这五种类型分别作为区分重载函数的参数以实现不同的移动和操作。

6.消除单纯传递调用的函数

观察书上advance的Forward iterator版本,因为其余input iterator动作相同,所以其直接就在函数体内调用了input iterator,其实这种调用也会消耗一定的资源,能不能消除这种单纯只做了传递调用的函数呢,可以的,只要两个类之间存在继承关系,那么子类在没有设计相应函数的情况下,就可以调用对父类设计的函数。

注意:这里所说的函数不是类的函数,而是把类作为参数的函数。

因为这五种类型间有各自的继承关系,例如Foward iterator继承了input iterator,有时候在两种类型行为一致的情况下就可以不设计Foward的函数了,编译器检测到Foward没有对应的函数,会自动调用input的函数的。

五.std::iterator的保证

为了符合规范,任何迭代器都应该提供上面提到的五种型别,为了设计迭代器时不遗漏,STL提供了一个规范的iterator class,我们之后设计的迭代器要继承这个class。设计适当的型别是迭代器本身的责任,而设计适当的迭代器是容器的责任。

六.iterator源代码完整重列

这一段代码没什么好说的,就是前面的代码缝合起来了,不过里面有两个函数让我有点疑惑,就是决定distance type和value type的两个函数,因为他返回的是一个指向地址为0的指针,我觉得他应该主要是想要指向两个相应类型的指针,之后对这两个指针重新赋值或做类型推导什么的。

7.SGI STL的私房菜:__type_traits

之前我们介绍了iterator_traits萃取迭代器的特性,而SGI STL又自己设计以一种__type_traits来萃取每种型别的特性。(注意这个SGI STL特有,不在STL标准规范之内)。

为什么要设计这个__type_traits呢,因为在进行一些操作的时候(例如复制等操作),有一些型别不止能使用默认的construct和destruct函数,他们有更高效的实现方式,所以我们设计 _ _type_traits来萃取类型的特性,看看他们是否有更高效的实现方式。

__type_traits的设计和iterator_traits类似,迭代器有五种特性,型别也有五种特性,我们要做的就是用 _ _type_traits把这五种特性(的真假)提取出来。

类似的,我们设计了一个通用的__type_traits,里面所有的萃取出来的特性都是false_type,这里就有和迭代器的萃取有不同了,迭代器的萃取是直接萃取迭代器的特性,因为迭代器的特性是设计在迭代器中的,而SGI STL并在STL标准规范之内,即C++中的型别并没有设计相应的特性,所以我们这里只有对要设计的每一种特性做 _ _type_traits的特化,一个个的设计型别对应的特性是true还是false。

如果之后我们自己定义了一种型别,因为SGI STL中没有对这种型别的特化,所以这种型别只能用效率最基础的函数了(如果编译器很聪明的话还是可以用高效函数的)。

关于型别的特性,POD以及型别什么时候是non-trival的,可以看下面这篇博客:

C++trivial和non-trivial构造函数及POD类型

PS:里面的true_type和false_type都是结构体,不是布尔类型的,因为要做类型推导,都是布尔类型的推导出来也都是布尔类型的了,区分不了,如果直接用true和false来区分感觉又有点破坏模板函数的一致性了。

八.小结

迭代器是什么,按我的理解,迭代器是类似于指针的一种东西。

迭代器怎么构造的呢,其实就是封装指针在一个类里,然后在类里写一些函数(这个主要要看容器里面怎么设计的)。

迭代器怎么用呢,算法通过迭代器来操作容器里的数据。

但是迭代器又不同与指针(或者说指针是特殊的迭代器),迭代器有很多特性和与迭代器相关的型别,我们设计了iterator_traits来提取这些特性(特性是迭代器本身设计的责任,我们做的只是提取),根据这些提取出来的特性我们可以用不同的函数去处理迭代器或容器。

每种型别又有各自的特性,这些特性主要是构造,析构,赋值,复制构造所要用的函数,如果自己设计了就调用,如果没有自己设计就调用高效率的函数,我们又设计type_traits来提取这些特性,不过这些特性不是本身设计好的,而是我们一个个特化设计的(因为这个并不是STL标准规范的内容)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STL实现迭代器萃取的方式是通过对iterator_traits进行偏特化。为了使迭代器萃取有效运行,每个迭代器都要自行以内嵌性别定义的方式定义出相应的型别。迭代器的类型萃取机制被定义为iterator_traits模板类,它接受一个迭代器类型作为模板参数,并通过特化来提取迭代器的相关信息。 在STL中,对于一般的迭代器类型,使用iterator_traits模板类进行萃取,通过迭代器的内嵌typedef来获取iterator_category、value_type、difference_type、pointer和reference等相应的型别。而针对原生指针类型的迭代器,STL会针对指针类型进行偏特化,直接定义出相应的型别,如random_access_iterator_tag、_Tp、ptrdiff_t、_Tp*和_Tp&等。这样就可以实现对迭代器的属性萃取。 通过对iterator_traits进行偏特化,STL能够根据不同的迭代器类型提取出相应的属性,从而能够在算法中正确地使用不同类型的迭代器,并根据迭代器的属性进行相应的操作。这样实现了迭代器的通用性和灵活性,使得STL算法可以适用于不同类型的迭代器。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [STL迭代器---萃取](https://blog.csdn.net/jiewaikexue/article/details/111372288)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [STL 迭代器萃取](https://blog.csdn.net/qq_40080842/article/details/128112675)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值