【C++ 源码剖析】迭代器设计原则和 iterator Traits 的作用

C++ 源码剖析

1. 迭代器设计原则

不论是泛型思维或STL的实际运用,选代器(iterators)都演着重要的角色。STL的中心思想在于:将数据容器 (containers)和算法(algorithms)分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起。而这胶着剂可以使用 类模板(class templates)和函数模板 (function templates) 来分别达成目标。

迭代器的定义如下:提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式。
迭代器类似于智能指针(smart pointer),但迭代器更专注于容器元素的遍历和访问。

2. 迭代器相应型别

iterator_category(迭代器类别):

描述迭代器的类别:如输入迭代器(input iterator)、输出迭代器(output iterator)、前向迭代器(forward iterator)、双向迭代器(bidirectional iterator)、随机访问迭代器(random iterator)。
例如,std::iterator_traits::iterator_category。

value_type(值类型):

描述迭代器指向的元素类型。
例如,std::iterator_traits::value_type。

difference_type(差值类型):

描述两个迭代器之间的距离(差值)的类型,通常是一个有符号整数类型。
例如,std::iterator_traits::difference_type。

pointer(指针类型):

描述迭代器所指向元素的指针类型。
例如,std::iterator_traits::pointer。

reference(引用类型):

描述迭代器所指向元素的引用类型。
例如,std::iterator_traits::reference。

3.Traits 编程技法

traits技法称为STL源代码的门钥,可见该内容重要性!

STL内部需要知道当前的迭代器的这些型别信息,其所使用的方法主要是模板的参数推导、模板内嵌型别以及模板偏特化。

模板参数推导:

STL 中的许多算法和容器是通过模板参数推导来获取迭代器的信息的。通过函数模板的参数推导,STL能够获取迭代器的类型信息,如迭代器的值类型、差值类型等。

模板内嵌型别(Type Traits):

STL 使用一系列的模板内嵌型别,例如 iterator_traits,以获取有关迭代器的详细信息。这些内嵌型别提供了对迭代器的特性的访问,如 value_type、difference_type、iterator_category 等。

模板偏特化:

STL 使用模板偏特化来为不同类型的迭代器提供不同的实现。
例如,iterator_traits 可能有多个偏特化版本,以处理指针类型、自定义迭代器类型等。

这些技术结合起来,使得STL能够在不同的迭代器类型上工作,并提供高度通用的算法和容器。通过使用这些技术,STL 能够在编译时确定迭代器的属性,从而进行有效的类型安全的泛型编程。

3.1. 问题的出现

泛型编程的目标之一是编写能够处理多种类型的通用代码,但有时不同类型可能没有共同的属性或接口,使得无法进行一般化。

当你编写泛型代码时,你不能假设传递给你的函数或模板的类型是迭代器。有时,它可能是原生指针,也可能是其他用户定义的类型。因为这些类型并没有定义自己相应的型别,如value_type,difference_type等,就无法进行合适的匹配。所以该泛化代码就无法一般化。

为了能够解决这一问题,就要用到模板偏特化的方法:

3.2. 模板偏特化

概念

模板偏特化(Template Partial Specialization) 是 C++ 中一种在模板编程中允许对模板的一部分进行特殊处理的技术。通常,它用于根据模板参数的特定属性或值,为模板提供更具体的实现。

在模板偏特化中,有两种主要形式:类模板偏特化和函数模板偏特化

3.2.1. 类模板偏特化:
// 主模板
template <typename T, typename U>
class MyClass {
    // 通用实现
};

// 部分特化,当 T 为 int 时
template <typename U>
class MyClass<int, U> {
    // 针对 T 为 int 的特殊实现
};

// 部分特化,当 U 为 double 时
template <typename T>
class MyClass<T, double> {
    // 针对 U 为 double 的特殊实现
};

3.2.2. 函数模板偏特化:
// 主模板
template <typename T>
void myFunction(T value) {
    // 通用实现
}

// 部分特化,当 T 为指针类型时
template <typename T>
void myFunction(T* ptr) {
    // 针对 T 为指针类型的特殊实现
}

// 部分特化,当 T 为特定类型时
template <>
void myFunction<int>(int value) {
    // 针对 T 为 int 的特殊实现
}

有了模板偏特化,就可以让traits萃取出原生指针(譬如vector的迭代器就是原生指针型别的)以及指向常量的原生指针型别。

负责萃取的就是 iterator_traits 模板,它用于提取迭代器的特性,以下是代码示例:

#include <type_traits>

// 主模板
template <typename Iterator>
struct iterator_traits {
    using iterator_category = typename Iterator::iterator_category;
    using value_type = typename Iterator::value_type;
    using difference_type = typename Iterator::difference_type;
    using pointer = typename Iterator::pointer;
    using reference = typename Iterator::reference;
};

// 如果不是以上五种型别,是原生指针或者其他,就需要使用部分特化。
// 当 Iterator 是指针类型时
template <typename T>
struct iterator_traits<T*> {
    using iterator_category = std::random_access_iterator_tag;
    using value_type = std::remove_cv_t<T>;
    using difference_type = std::ptrdiff_t;
    using pointer = T*;
    using reference = T&;
};

// 示例用法
int main() {
    using iterator_type = int*;
    using traits = iterator_traits<iterator_type>;

    // 可以使用 traits 中的成员类型
    typename traits::iterator_category category;  // 迭代器类别
    typename traits::value_type value_type;        // 元素类型
    typename traits::difference_type difference;   // 差值类型
    typename traits::pointer ptr;                  // 指针类型
    typename traits::reference ref;                // 引用类型

    return 0;
}

根据以上代码,就可以知道模板偏特化可以解决这个问题,将原生指针匹配到模板特化版本上,然后就可以将原生指针(或者是其他类型)进行特征提取(value_type,difference_type等等),以达到泛化的目的。

3.3. iterator_traits偏特化相关源码:

以下是用于区分输入类型原生指针还是迭代器,并提供相应的迭代器特性(traits)源码片段

template <class _Iter>
struct _Iterator_traits_base<_Iter,
    void_t<typename _Iter::iterator_category, typename _Iter::value_type, typename _Iter::difference_type,
        typename _Iter::pointer, typename _Iter::reference>> {
    // defined if _Iter::* types exist
    using iterator_category = typename _Iter::iterator_category;
    using value_type        = typename _Iter::value_type;
    using difference_type   = typename _Iter::difference_type;
    using pointer           = typename _Iter::pointer;
    using reference         = typename _Iter::reference;
};

template <class _Ty, bool = is_object_v<_Ty>>
struct _Iterator_traits_pointer_base { // iterator properties for pointers to object
    using iterator_category = random_access_iterator_tag;
    using value_type        = remove_cv_t<_Ty>;
    using difference_type   = ptrdiff_t;
    using pointer           = _Ty*;
    using reference         = _Ty&;
};

这段代码是用于迭代器特性(traits)的定义,其中包含了两个部分:

1. _Iterator_traits_base 结构体模板:

这个模板结构体用于从迭代器 _Iter 中提取迭代器的基本特性。
使用了 void_t 模板工具,该工具用于检查 _Iter 是否定义了 iterator_category、value_type、difference_type、pointer 和 reference 这些成员类型。
如果 _Iter 中存在这些成员类型,那么这个结构体定义了相应的特性。

2. _Iterator_traits_pointer_base 结构体模板:

这个模板结构体用于定义指向对象的指针的迭代器特性。
根据模板参数 _Ty 的类型,决定迭代器的特性。
如果 _Ty是对象类型(is_object_v<_Ty> 为 true),则定义了iterator_category、value_type、difference_type、pointer 和 reference 这些成员类型,其中 pointer 是 _Ty*,reference 是 _Ty&。 remove_cv_t<_Ty> 是一个 C++ 类型转换工具,用于从类型 _Ty 中移除 const 和 volatile 修饰符。
如果 _Ty不是对象类型,则这是一个特化版本,但没有定义任何成员,相当于对指向非对象的指针的迭代器,这种迭代器并不具备特定的迭代器特性。

3.4. 迭代器之间的层次结构是通过继承来实现

这主要是为了允许泛型编程中的算法在不同的迭代器类别上进行操作,并根据迭代器的类别选择合适的实现。

3.4.1. 迭代器的层次结构标签:
_EXPORT_STD struct input_iterator_tag {};

_EXPORT_STD struct output_iterator_tag {};

_EXPORT_STD struct forward_iterator_tag : input_iterator_tag {};

_EXPORT_STD struct bidirectional_iterator_tag : forward_iterator_tag {};

_EXPORT_STD struct random_access_iterator_tag : bidirectional_iterator_tag {};

这种继承关系使得泛型算法能够在迭代器类别之间进行选择,并使用最适合的实现。例如,如果一个算法只需要单向遍历,那么它只需要接受 input_iterator_tag 类别的迭代器。如果算法需要随机访问,那么它可以接受 random_access_iterator_tag 类别的迭代器。

通过这种继承结构,算法可以利用编译时的多态性,选择最适合的实现路径。这样的设计使得算法能够更加灵活、通用,并且在不同的情况下能够以最优的方式工作。

3.4.2. distance为例:
#include <iterator>

// 通用实现
template <typename InputIterator>
typename std::iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last, std::input_iterator_tag) {
    typename std::iterator_traits<InputIterator>::difference_type n = 0;
    while (first != last) {
        ++first; ++n;
    }
    return n;
}

// 针对随机访问迭代器的特殊实现
template <typename RandomAccessIterator>
typename std::iterator_traits<RandomAccessIterator>::difference_type
distance(RandomAccessIterator first, RandomAccessIterator last, std::random_access_iterator_tag) {
    return last - first;
}

// 入口函数,根据迭代器类别选择合适的实现
template <typename Iterator>
typename std::iterator_traits<Iterator>::difference_type
distance(Iterator first, Iterator last) {
    return distance(first, last, typename std::iterator_traits<Iterator>::iterator_category());
}

int main() {
    // 使用 distance 函数计算距离
    int array[] = {1, 2, 3, 4, 5};
    auto result = distance(std::begin(array), std::end(array));

    return 0;
}

  1. distance 函数被重载了两次,一次用于处理输入迭代器,另一次用于处理随机访问迭代器。
  2. distance 入口函数会根据传递的迭代器的类别选择正确的具体实现,通过调用 distance 函数的第三个参数(iterator_category)进行实现的选择。

如果传递的是 forward_iterator 类型,编译器会根据函数调用 distance(first, last, category()) 进行模板参数推导,并根据参数匹配选择正确的函数模板。如果传递的是随机访问迭代器,那么编译器将选择 random_access_iterator_tag;如果传递的是双向迭代器,将选择 bidirectional_iterator_tag,以此类推。

这个匹配过程是自动进行的,是通过模板推导和重载决议机制实现的。编译器会根据传递的实际类型来选择最合适的匹配,这种机制使得泛型算法能够适应多种不同类型的迭代器。

对于 forward_iterator,它继承了 input_iterator_tag,而 input_iterator_tag 是 distance 函数模板中的一个函数参数,它的类型为 input_iterator_tag。因此,编译器会选择匹配的重载版本,即:

template <typename InputIterator>
typename std::iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last, std::input_iterator_tag) {
    // ...
}

因此,如果传递的是 forward_iterator 类型,最终会匹配到 distance 函数模板中的这个版本。这个版本的实现会使用输入迭代器的方式计算迭代器的距离。

4. 总结

通过 traits 编程技法,我们将函数模板对于原生指针和自定义 iterator 的定义都统一起来,我们使用 traits 技法主要是为了解决原生指针和自定义 iterator 之间的不同所造成的代码冗余,这就是 traits 技法的妙处所在。


C++ 源码学习笔记
欢迎关注🔎点赞👍收藏⭐️留言📝

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值