Skip to main content

Chapter 6: Iterator System

STL 的 iterator system 把“容器里元素怎么存放”转换成“算法可以怎样向前走、读、写、回退和跳转”的位置接口。本章的目标是让读者能定位 iterator 在容器和算法之间承担的责任,判断一个 iterator 能提供哪些操作,追踪 traits 和 adapter 如何改变算法实现路径,并能写出最小 iterator 与 reverse_iterator

本章贯穿一个短操作:从一个 std::vector<int> 中遍历元素、复制元素、反向访问元素,并讨论这些操作在 std::list<int>、普通指针和输出适配器上会发生什么变化。这个材料足够小,但覆盖了 iterator 的主要问题:位置对象、区间边界、能力分层、编译期分发、适配层和失效边界。

#include <algorithm>
#include <iterator>
#include <list>
#include <vector>

int main() {
std::vector<int> numbers{1, 2, 3, 4};
std::vector<int> copied;

auto first = numbers.begin();
auto last = numbers.end();

auto even_count = std::count_if(first, last, [](int value) {
return value % 2 == 0;
});

std::copy(first, last, std::back_inserter(copied));

auto rfirst = std::make_reverse_iterator(last);
auto rlast = std::make_reverse_iterator(first);
auto first_from_back = *rfirst;

(void)even_count;
(void)first_from_back;
}

这段代码里,std::count_if 只需要能从 first 走到 last 并读取元素;std::copy 的输出端可以是 std::back_inserter(copied),它看起来像 iterator,实际写入动作会转成 copied.push_back(value)std::make_reverse_iterator(last) 用正向区间的尾后位置构造反向起点。读 STL 源码时,iterator system 的主问题就是:算法看到的是哪些表达式可用,容器承诺的是哪些位置状态有效,traits 和 adapter 怎样把这些信息交给泛型代码。

6.1 iterator 作为 range cursor

iterator 的工作定义是:它是指向某个序列位置的值对象,算法通过复制、比较、递增、解引用等表达式操作它,从而访问序列中的元素。这里的“位置”比裸地址更宽:std::vector<int>::iterator 常见实现形状接近指针,std::list<int>::iterator 常见实现形状保存节点指针,std::istream_iterator<int> 则把输入流当前读取状态包装成位置对象。

STL 算法通常接收半开区间 [first, last)first 指向第一个候选元素,last 表示尾后位置,尾后位置可比较、可作为停止条件,解引用尾后位置没有定义好的结果。这个约定让空区间可以自然表达为 [first, first),也让算法循环保持统一形状。

template <class Iterator, class Predicate>
int count_matches(Iterator first, Iterator last, Predicate pred) {
int count = 0;
for (; first != last; ++first) {
if (pred(*first)) {
++count;
}
}
return count;
}

这段简化代码展示了算法对 iterator 的最小期待:能比较 first != last,能执行 ++first,能执行 *first,并且从 first 经过有限次递增能到达 last。这些期待构成“有效区间”的运行时前提。调用者传入来自两个不同容器的 iterator,或者传入无法到达的边界,算法内部通常没有额外证据能修复这个错误。

iterator 作为 range cursor 时有三个状态需要分开。可解引用位置指向真实元素,表达式 *it 有意义;尾后位置属于同一序列的边界,适合比较和停止;singular 位置没有关联到一个可用序列,例如默认构造出来但尚未赋值的某些 iterator。STL 正文和参考资料会把无效 iterator 归入可能 singular 的范围,工程判断上应把它当作失效位置处理。

下面这张图只描述算法看到的区间边界,暂时不讨论容器内部如何存储元素。

图中的关键点是:算法并不拥有容器,也不需要知道 numbers 内部是三指针状态、链表节点还是输入流缓冲。算法只沿着 iterator 表达式前进。容器负责制造 begin()end() 这些位置对象,并承诺在没有触发失效规则时,这些位置能描述同一个序列。

贯穿代码中的 firstlast 来自 numbers.begin()numbers.end()std::count_if(first, last, pred) 读取这个区间,不改变 numbers 的结构;只要 numbers 在算法运行期间没有发生使位置失效的修改,firstlast 的关系就是稳定的。换成 std::list<int> numbers{1, 2, 3, 4}; 时,算法循环的表面表达式不变,iterator 内部状态从连续地址位置变成节点位置。

源码阅读时,先看一个算法的循环体使用了哪些 iterator 表达式。只出现 ++itit != last*it,说明算法的核心路径只依赖输入遍历能力;出现 --it,说明它需要双向能力;出现 it + nlast - firstit[n],说明它进入随机访问路径。这个判断比先记住算法名称更稳定。

6.2 pointer、iterator category 与能力分层

普通指针是 iterator system 的基础参照物。int* 可以指向数组元素,可以解引用,可以递增,可以比较,可以做差,也可以用下标访问;因此很多泛型算法对 int*std::vector<int>::iterator 使用同一套写法。iterator 抽象把这些指针式表达式推广到更多数据结构:链表 iterator 可以递增和回退,输入流 iterator 可以向前读取,插入适配器可以接收写入。

iterator category 描述的是“该 iterator 支持哪些操作和语义承诺”。旧式 STL 使用 tag 类型表达能力层级,C++20 之后增加 iterator concepts,用约束表达可用操作与语义条件。两套体系的名称接近,源码中常常同时出现:旧算法实现可能检查 iterator_category,现代 ranges 算法更倾向用 concept 约束。

能力层级典型对象核心操作工程含义
output iteratorstd::back_insert_iterator<std::vector<int>>写入、递增适合作为算法输出端,读取旧值通常没有意义
input iteratorstd::istream_iterator<int>读取、递增、单趟遍历适合消费输入流或一次性序列
forward iteratorstd::forward_list<int>::iterator读取、递增、多趟遍历可保存位置并重复遍历同一区间
bidirectional iteratorstd::list<int>::iteratorforward 能力加 --it支撑反向移动、双向链表遍历
random access iteratorstd::deque<int>::iteratorbidirectional 能力加 + n- n、下标支撑按距离跳转和常数时间距离计算
contiguous iteratorstd::vector<int>::iteratorint*random access 能力加连续对象存储支撑按地址连续访问和与底层数组模型对接

这张表的排序依据是表达式能力和语义承诺的增强。std::sort 需要随机访问能力,因为它要在区间内按距离跳转和交换;std::list<int> 支持双向遍历,但缺少常数时间跳转,所以 list 提供成员函数 list::sort() 来利用节点重连完成排序。这个分工不是 API 风格差异,而是底层 iterator 能力直接限制算法形状。

贯穿代码中的 numbers.begin() 来自 std::vector<int>,通常满足随机访问和连续存储相关能力。于是下面的表达式成立:

std::vector<int> numbers{1, 2, 3, 4};
auto it = numbers.begin();
int third = it[2];
auto distance = numbers.end() - numbers.begin();

如果把容器改为 std::list<int>it[2]end() - begin() 这类表达式没有对应能力。链表的节点在内存中分散,iterator 的移动路径依赖 next / prev 指针逐步前进;它能表达顺序访问,无法把“跳过两个节点”压缩成一次指针加法。

contiguous iterator 增加的是对象地址连续条件。std::vector<int>std::array<int, N> 和 C 数组指针可以把相邻元素映射到相邻地址;std::deque<int> 虽然支持随机访问表达式,常见实现使用分段缓冲区,跨块时需要控制结构参与定位,因此不提供同样的连续地址承诺。性能分析时,random access 说明“能按下标定位”,contiguous 说明“元素在地址上连续”。这两个判断服务不同问题。

输出 iterator 的能力方向相反。std::back_inserter(copied) 的核心动作是接收赋值:

std::vector<int> copied;
auto out = std::back_inserter(copied);
*out = 42;
++out;
*out = 43;

这段代码的效果是向 copied 追加两个元素。outoperator* 返回适配器自身,operator= 调用容器的 push_back。它满足“算法可以写入”的接口表面,但它没有指向一个已经存在的元素。读源码时看到输出 iterator,应优先检查写入表达式会被转成哪种容器修改动作。

判断 iterator 能力的顺序可以固定为四步:先看是否能读取或写入;再看递增之后是否支持多趟保存;然后看是否支持回退和按距离跳转;最后看是否承诺连续地址。这个顺序能直接解释算法选择、复杂度差异和容器适配边界。

6.3 iterator_traits 与 tag dispatch

泛型算法拿到的是模板参数 Iterator,它需要从这个类型里取出 value type、difference type、reference type、pointer type 和 category。std::iterator_traits<Iterator> 承担这层类型查询。对于自定义 iterator,traits 通常读取 iterator 类型内部定义的嵌套类型;对于普通指针,标准库提供特化,让 int* 也能进入同一套算法路径。

下面的简化代码展示了 traits 的典型使用方式:

#include <iterator>
#include <type_traits>

template <class Iterator>
using iter_value_type = typename std::iterator_traits<Iterator>::value_type;

template <class Iterator>
iter_value_type<Iterator> read_first(Iterator first) {
return *first;
}

static_assert(std::is_same_v<iter_value_type<int*>, int>);

read_first<int*> 能工作,关键不在指针本身有嵌套类型,而在 std::iterator_traits<int*> 有指针特化。这个设计让“普通指针”和“类类型 iterator”在泛型算法中拥有统一的类型查询入口。读源码时,看到 typename iterator_traits<_Iter>::value_type,要把它理解为算法向 iterator 查询“元素值类型”,不是容器直接暴露的成员类型。

tag dispatch 是旧式 STL 中常见的编译期路径选择方式。算法先通过 traits 得到 category tag,再把 tag 作为一个空对象传给重载函数,编译器根据 tag 类型选择实现。std::advance 是最典型的阅读入口:对 input iterator 只能循环递增;对 bidirectional iterator 可以根据正负距离递增或递减;对 random access iterator 可以一次执行 it += n

#include <iterator>

template <class Iterator, class Distance>
void advance_impl(Iterator& it, Distance n, std::input_iterator_tag) {
while (n > 0) {
--n;
++it;
}
}

template <class Iterator, class Distance>
void advance_impl(Iterator& it, Distance n, std::bidirectional_iterator_tag) {
if (n >= 0) {
while (n > 0) {
--n;
++it;
}
} else {
while (n < 0) {
++n;
--it;
}
}
}

template <class Iterator, class Distance>
void advance_impl(Iterator& it, Distance n, std::random_access_iterator_tag) {
it += n;
}

template <class Iterator, class Distance>
void mini_advance(Iterator& it, Distance n) {
using category = typename std::iterator_traits<Iterator>::iterator_category;
advance_impl(it, n, category{});
}

这段代码的结论是:iterator category 影响的是算法实现形状和复杂度。mini_advancestd::vector<int>::iterator 可以走 it += n,对 std::list<int>::iterator 需要一步步移动。算法名称相同,编译期类型不同,生成路径就不同。

C++20 concepts 把很多 tag dispatch 的隐式分支改成约束表达。例如一个函数可以写成 template <std::random_access_iterator Iterator>,直接要求实参类型满足随机访问 iterator 的 concept。概念约束让错误更早暴露,也让接口条件更接近函数签名;但读旧 STL 源码仍要能识别 traits 加 tag 的路径。

iterator traits 的工程价值还体现在错误定位。自定义 iterator 如果缺少 difference_type,某些算法在计算距离或偏移时会实例化失败;如果 category 声明过强,例如把只能单向移动的 iterator 标成 random access,算法可能生成 it += n 这类表达式,随后编译失败或产生错误语义。实现 iterator 时,category 是对外承诺,必须和实际操作集合一致。

源码阅读可以按固定顺序处理 traits 代码:先找到算法模板参数;再找 iterator_traits 查询了哪些关联类型;然后找 category tag 或 concept 约束;最后把选中的实现路径和复杂度承诺对应起来。这个顺序能把大量模板代码压缩成“算法向 iterator 查询能力并选择路径”的单一主线。

6.4 标准 iterator adapter

iterator adapter 的作用是保留 iterator 风格的接口,同时改变底层动作。它们不是容器,也不拥有一整段元素;它们包装一个 iterator、一个容器指针或一个流对象,让算法在同一套 *it++itit == end 表达式下完成反向遍历、插入写入或流式读写。

std::reverse_iterator 是最容易读错的 adapter。它内部保存一个 base iterator,解引用时访问 base 前一个位置。这个设计让反向区间也能使用半开区间:std::make_reverse_iterator(numbers.end()) 对应最后一个元素,std::make_reverse_iterator(numbers.begin()) 对应反向尾后位置。

std::vector<int> numbers{1, 2, 3, 4};

auto rfirst = std::make_reverse_iterator(numbers.end());
auto rlast = std::make_reverse_iterator(numbers.begin());

int value = *rfirst; // 4
auto base = rfirst.base(); // numbers.end()

这里 rfirst.base() 返回的是正向尾后位置,*rfirst 读取的是它前面的元素。这个 off-by-one 关系服务半开区间一致性:正向 [begin, end) 覆盖所有元素,反向 [make_reverse_iterator(end), make_reverse_iterator(begin)) 也覆盖所有元素。读源码或调试时,把 base() 当作“当前元素位置”会导致边界判断错位。

插入 iterator adapter 把算法输出端转换成容器修改动作。std::back_inserter(copied) 返回 std::back_insert_iterator,赋值时调用 copied.push_back(value)std::front_inserter(container) 调用 push_frontstd::inserter(container, pos) 调用 insert(pos, value) 并更新内部位置。它们的共同点是:算法写入的是 iterator 表面,实际变化发生在目标容器上。

std::vector<int> source{1, 2, 3};
std::vector<int> target;

std::copy(source.begin(), source.end(), std::back_inserter(target));

这段代码把 std::copy 的输出端从“覆盖已有区间”改成“追加元素”。如果写成 std::copy(source.begin(), source.end(), target.begin()),调用者必须保证 target 已经有足够元素可被赋值;使用 back_inserter 时,目标容器通过 push_back 增长,元素生命周期由容器修改接口负责建立。

stream iterator adapter 把输入输出流包装成 iterator 风格接口。std::istream_iterator<int> 从流中按格式读取整数;std::ostream_iterator<int> 把赋值动作转成输出流写入。它们让 std::copy 可以连接流和容器:

#include <iostream>
#include <iterator>
#include <vector>

void print_numbers(const std::vector<int>& numbers) {
std::copy(numbers.begin(), numbers.end(), std::ostream_iterator<int>(std::cout, " "));
}

这段代码里,输出 iterator 的目标不是内存中的元素位置,而是 std::cout 的写入状态。算法仍然只看到赋值和递增表达式。adapter 把“写到哪里”从算法中抽离出来,算法只保持“从输入区间取值,写入输出 iterator”的结构。

adapter 的边界来自被包装对象。reverse_iterator 需要底层 iterator 至少能回退;front_inserter 需要容器提供 push_frontback_inserter 需要容器提供 push_back;stream iterator 的正确性依赖流状态。读源码时,看到 adapter 先问它包装了什么,再问它把 operator*operator++operator= 映射到哪些底层动作。

6.5 iterator 失效与算法解耦

iterator 失效指的是原来描述某个序列位置的 iterator 值,在容器修改后不再能安全描述同一位置。失效规则由容器和修改操作决定;算法只接收区间并按接口前进。这个分工解释了 STL 的解耦边界:算法复用性来自 iterator 接口,位置安全性仍由调用者按容器规则维护。

贯穿代码中,firstlast 保存了 numbers 的旧位置。如果在使用它们之前触发 std::vector 重新分配,旧位置会指向旧存储区域;继续解引用旧 iterator 就进入错误路径。

std::vector<int> numbers{1, 2, 3, 4};
auto first = numbers.begin();
auto last = numbers.end();

numbers.push_back(5); // 可能触发重新分配

// 此处继续使用 first 或 last 需要先根据 vector 的容量变化判断有效性。

std::vector 的核心状态是连续存储区加大小和容量。push_back 在容量足够时只在尾部构造新元素,旧元素地址通常保持;当容量不足时,容器会分配新存储、移动或拷贝旧元素、销毁旧存储上的元素并释放旧存储。后一条路径改变了元素地址,所以旧 iterator、指针和引用都失去原位置依据。

不同容器的失效规律来自不同存储形状。std::list 的元素位于独立节点,插入新节点通常不移动已有节点,所以指向已有元素的 iterator 可继续描述原节点;删除某个节点会使指向该节点的 iterator 失效。std::deque 使用分段存储和控制结构,两端插入、内部插入、控制结构扩展会带来更细的失效边界。后续容器章节会展开细则,本章先建立判断方法:先看修改是否移动已有元素或替换控制结构,再看旧 iterator 是否依赖被改变的状态。

修改现象位置对象依赖典型后果
连续容器重新分配元素地址、尾后位置、容量边界旧 iterator、引用、指针需要按失效处理
连续容器中间插入或删除插入点之后的元素顺序和地址插入点之后的位置关系改变
链表插入新节点已有节点地址和链接关系指向已有节点的位置通常保持
链表删除节点被删除节点本身指向该节点的位置失效
哈希容器 rehashbucket 数组和节点分桶关系iterator 路径需要重新取得

算法解耦的代价是算法不验证容器修改历史。std::count_if(first, last, pred) 接受两个 iterator,并假设它们构成有效区间。它不会知道这两个 iterator 是否来自同一个旧版本的容器,也不会知道中途是否有其它代码修改容器。并发修改、回调中修改容器、循环中保存旧 iterator 后继续使用,都是工程代码里常见的失效来源。

安全的判断顺序可以固定为五步。第一步,确认 iterator 来自哪个容器实例和哪个序列版本。第二步,列出从取得 iterator 到使用 iterator 之间发生的容器修改。第三步,根据容器存储形状判断这些修改是否移动元素、删除目标元素或替换控制结构。第四步,重新取得 begin()end() 或目标位置。第五步,把算法调用限制在当前有效区间内。

失效和异常安全也有关。某些插入操作如果在分配或构造阶段失败,容器会按照异常安全承诺恢复到可用状态;但已经被判定失效的 iterator 不会因为异常路径自动恢复成可用位置。工程代码应把“容器对象仍可用”和“旧 iterator 仍可用”分开判断。

6.6 mini iterator 与 mini reverse_iterator 实现路径

实现 mini iterator 的目标是把前面几节的抽象压回类型、状态和运算。一个最小随机访问 iterator 可以用原始指针作为内部状态,对外提供解引用、递增、比较、加法、减法和 traits 所需嵌套类型。教学实现不代表真实标准库源码,但它能显示常见实现形状中最关键的接口边界。

#include <cstddef>
#include <iterator>

template <class T>
class MiniVectorIterator {
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;

MiniVectorIterator() = default;
explicit MiniVectorIterator(T* current) : current_(current) {}

reference operator*() const { return *current_; }
pointer operator->() const { return current_; }

MiniVectorIterator& operator++() {
++current_;
return *this;
}

MiniVectorIterator operator++(int) {
MiniVectorIterator old = *this;
++(*this);
return old;
}

MiniVectorIterator& operator--() {
--current_;
return *this;
}

MiniVectorIterator& operator+=(difference_type offset) {
current_ += offset;
return *this;
}

MiniVectorIterator operator+(difference_type offset) const {
MiniVectorIterator copy = *this;
copy += offset;
return copy;
}

difference_type operator-(MiniVectorIterator other) const {
return current_ - other.current_;
}

reference operator[](difference_type offset) const {
return current_[offset];
}

friend bool operator==(MiniVectorIterator left, MiniVectorIterator right) {
return left.current_ == right.current_;
}

friend bool operator!=(MiniVectorIterator left, MiniVectorIterator right) {
return !(left == right);
}

private:
T* current_ = nullptr;
};

这个 iterator 的状态只有 current_operator* 把位置转成元素引用,operator++ 改变位置,operator- 把两个位置转成距离,嵌套类型让 std::iterator_traits 可以查询能力。它声明为 std::random_access_iterator_tag,因为它的状态是连续对象指针,能支持常数时间跳转和距离计算。

如果同样写一个链表 iterator,内部状态通常是节点指针,operator++ 走向 node->nextoperator-- 走向 node->prev。它可以声明 bidirectional 能力;如果把它声明成 random access,就必须实现 operator+operator-、下标等表达式,并承担对应复杂度语义。类型声明和运行时状态必须相互匹配。

mini reverse_iterator 的关键是保存 base iterator,并在解引用时读取 base 前一个位置。下面的代码只覆盖双向 iterator 所需路径,省略了完整标准接口中的比较、距离和多种转换构造。

#include <iterator>

template <class Iterator>
class MiniReverseIterator {
public:
using iterator_type = Iterator;
using traits = std::iterator_traits<Iterator>;
using value_type = typename traits::value_type;
using difference_type = typename traits::difference_type;
using pointer = typename traits::pointer;
using reference = typename traits::reference;

MiniReverseIterator() = default;
explicit MiniReverseIterator(Iterator base) : base_(base) {}

Iterator base() const { return base_; }

reference operator*() const {
Iterator copy = base_;
--copy;
return *copy;
}

MiniReverseIterator& operator++() {
--base_;
return *this;
}

MiniReverseIterator& operator--() {
++base_;
return *this;
}

friend bool operator==(MiniReverseIterator left, MiniReverseIterator right) {
return left.base_ == right.base_;
}

private:
Iterator base_{};
};

MiniReverseIteratoroperator++ 执行 --base_,因为反向遍历向前走一步,对应底层正向 iterator 回退一步。operator* 复制 base 并先回退再解引用,这保持了 base() 的尾后边界语义。这个实现要求底层 iterator 支持 --,所以它不能包装只有 input 或 forward 能力的 iterator。

把 mini iterator 接回一个最小容器时,容器只需要用内部存储边界构造 iterator。下面代码展示接口形状:

template <class T>
class MiniArrayView {
public:
using iterator = MiniVectorIterator<T>;

MiniArrayView(T* data, std::size_t size) : data_(data), size_(size) {}

iterator begin() { return iterator(data_); }
iterator end() { return iterator(data_ + size_); }

private:
T* data_ = nullptr;
std::size_t size_ = 0;
};

这个 view 没有拥有元素,也没有处理分配。它只把 data_size_ 转成 [begin, end) 区间。完整容器还要管理对象生命周期、容量、异常安全和失效规则;iterator 只负责把当前位置暴露给算法。把这条责任边界分清之后,读 vectorlistdeque 的 iterator 源码时,就能先找内部状态,再找位置运算,再回到容器修改操作检查失效边界。

本章最终的判断顺序是:先确认 iterator 描述的序列和区间是否有效;再确认 iterator 能力层级;然后看 traits 或 concept 如何把能力交给算法;接着识别 adapter 是否改变底层读写动作;最后根据容器修改路径判断旧位置是否仍能使用。这个顺序覆盖了算法调用、源码阅读和自定义 iterator 实现三个场景。

最小自检任务

阅读下面代码,回答四个问题:std::copy 的输入端和输出端分别需要什么 iterator 能力;std::advance(it, 2)std::vector<int>std::list<int> 上复杂度为何不同;r.base() 指向哪个正向位置;savedpush_back 之后是否能继续解引用应如何判断。

#include <algorithm>
#include <iterator>
#include <list>
#include <vector>

int main() {
std::vector<int> source{1, 2, 3};
std::vector<int> target;

auto saved = source.begin();
auto r = std::make_reverse_iterator(source.end());

std::copy(source.begin(), source.end(), std::back_inserter(target));
std::advance(saved, 2);

source.push_back(4);

std::list<int> nodes{1, 2, 3};
auto node_it = nodes.begin();
std::advance(node_it, 2);

(void)r;
}

答案要点

std::copy 的输入端需要能从 source.begin() 递增到 source.end() 并读取 *it,输出端 std::back_inserter(target) 需要能接收赋值,赋值动作转成 target.push_back(value)std::advance(saved, 2)std::vector<int>::iterator 可走随机访问路径,通常是常数时间偏移;对 std::list<int>::iterator 只能沿节点执行两次递增,复杂度随移动距离增长。rsource.end() 构造,r.base() 返回正向尾后位置,*r 读取最后一个元素。source.push_back(4) 后,saved 是否能继续解引用取决于这次追加是否触发 vector 重新分配;工程代码应在修改后重新取得需要的位置,尤其是无法证明容量足够时。

本章知识点总结

  • 位置接口:iterator 把容器内部存储转换成算法可使用的位置对象。
  • 半开区间[first, last) 用尾后位置表达边界,算法在到达 last 时停止。
  • 有效区间last 必须能从 first 经过有限次递增到达,失效位置不能组成可靠输入。
  • 指针模型:普通指针提供 iterator 的基础表达式,STL 把这组表达式推广到容器、流和适配器。
  • 能力层级:iterator category 或 concept 描述读取、写入、递增、回退、跳转和连续存储承诺。
  • traits 查询std::iterator_traits 让算法从 iterator 类型取得 value type、difference type 和 category。
  • 分发路径:tag dispatch 或 concept 约束把 iterator 能力转成不同算法实现和复杂度。
  • 反向适配reverse_iterator 保存 base iterator,解引用时访问 base 前一个位置。
  • 插入适配back_inserterfront_inserterinserter 把输出赋值转成容器插入动作。
  • 失效判断:iterator 是否仍可用由容器修改路径决定,连续存储重新分配会改变旧位置依据。
  • 实现边界:mini iterator 的核心是内部状态、关联类型、位置运算和与声明能力一致的语义。
  • 阅读顺序:先看区间有效性,再看能力层级,再看 traits 分发,再看 adapter 映射,最后查容器失效规则。