概念

适配器就是Interface(接口),对容器、迭代器和算法进行包装,但其实质还是容器、迭代器和算法,只是不依赖于具体的标准容器、迭代器和算法类型,容器适配器可以理解为容器的模板,迭代器适配器可理解为迭代器的模板,算法适配器可理解为算法的模板。

容器适配器

容器适配器(Container Adapters)是对现有容器的封装,提供特定的接口来实现特殊的数据结构。STL提供了三种主要的容器适配器:

stack(栈)

基本用法

主要成员函数

底层容器

使用stack实现括号匹配

queue(队列)

基本用法

主要成员函数

底层容器

使用queue实现BFS

priority_queue(优先队列)

基本用法

自定义比较器

主要成员函数

底层容器

使用priority_queue实现Dijkstra算法

总结

共同特点

  1. 封装性:隐藏底层容器的复杂性

  2. 接口限制:只提供特定操作,确保数据结构的完整性

  3. 无迭代器:不提供迭代器访问,保持数据结构的抽象性

  4. 底层容器可选:可以选择不同的底层容器实现

底层容器要求

性能

迭代器适配器

迭代器适配器(Iterator Adapters)是对现有迭代器的封装,提供特殊的功能和行为。主要包括插入迭代器、反向迭代器、移动迭代器和流迭代器等。

插入迭代器

插入迭代器(Insert Iterators)将赋值操作转换为插入操作,主要有三种类型:

back_insert_iterator

在容器尾部插入元素:

front_insert_iterator

在容器前端插入元素(仅支持deque、list等):

insert_iterator

在指定位置插入元素:

插入迭代器的特点

第6行和第9行完全等价,关键在于理解 back_insert_iterator内部实现机制。虽然递增操作"无效果",但它们仍然返回有效的迭代器对象,这使得连续操作成为可能。

这个表达式的执行顺序:

  1. back_it++ 执行后置递增,返回 *this(迭代器自身的副本)

  2. * 对返回的迭代器解引用,又返回迭代器自身

  3. = 100 调用赋值操作符,执行 container->push_back(100)

关键点:返回的是有效对象

虽然递增操作不改变迭代器的内部状态,但它们都返回有效的迭代器对象

"无效果"指的是:

不是指:

虽然递增操作对迭代器状态"无效果",但它们保证返回功能完整的迭代器对象,这是插入迭代器设计的巧妙之处。

既然 *back_it 和 *back_it++ 这两种写法功能完全相同,为什么还需要++?

功能角度来看,对于 back_insert_iterator 确实不写 ++ 也能达到一样的插入效果。但是设计上提供 ++ 操作符有重要的原因:

  1. 迭代器概念的完整性 插入迭代器必须满足 OutputIterator 的概念要求,而 OutputIterator 要求必须支持:

    • *it = value(赋值)

    • ++it(前置递增)

    • it++(后置递增)

  2. 与 STL 算法的兼容性,许多 STL 算法都使用 *it++ 这种惯用法。std::copy 内部会使用 *result++ 的形式,如果 back_inserter 不支持 ++,这个调用就会失败。

  3. 统一的编程接口,提供 ++ 让插入迭代器与其他迭代器有统一的使用方式。

反向迭代器

反向迭代器(Reverse Iterators)使得容器可以反向遍历:

移动迭代器

C++11引入,用于移动语义:

流迭代器

istream_iterator

从输入流读取数据:

ostream_iterator

向输出流写入数据:

流迭代器也是适配器

流迭代器确实是迭代器,但也可以归类为迭代器适配器。原因:

  1. 适配不同的数据源 流迭代器将流(stream)这种数据源适配成了迭代器接口

  2. 桥接两种不同的抽象

    • 流(Stream):面向字符/字节的顺序读写抽象

    • 迭代器(Iterator):面向元素的遍历抽象

流迭代器充当了这两种抽象之间的桥梁。

所有迭代器适配器都有类似的特点:

适配器类型适配的对象提供的新接口
reverse_iterator普通迭代器反向遍历接口
insert_iterator容器插入操作的迭代器接口
move_iterator普通迭代器移动语义的迭代器接口
istream_iterator输入流输入迭代器接口
ostream_iterator输出流输出迭代器接口

普通迭代器

迭代器适配器

流迭代器被称为"适配器"是因为它们的主要作用是适配——将流这种非迭代器接口的对象包装成迭代器接口,从而能够与 STL 的算法和容器无缝配合。这体现了适配器模式的核心思想:让不兼容的接口能够协同工作。

所以流迭代器既是迭代器(实现了迭代器接口),也是适配器(适配了流接口),这两个身份并不矛盾。

应用

数据处理管道

容器合并

高效的容器转移

总结

  1. 插入迭代器back_inserter, front_inserter, inserter

  2. 反向迭代器reverse_iterator, rbegin(), rend()

  3. 移动迭代器move_iterator, make_move_iterator

  4. 流迭代器istream_iterator, ostream_iterator

算法适配器

算法适配器是(Algorithm Adapters)STL中用于修改或增强算法行为的工具,它们通过包装函数对象来改变算法的执行方式。

std::bind

基本使用

std::bind 是最重要的函数适配器,用于绑定函数参数:

绑定成员函数

占位符

占位符(Placeholders)是 std::bind 的核心机制,用于指定在调用绑定函数时哪些参数位置需要由调用者提供。

占位符定义在 std::placeholders 命名空间中,用于标记参数的位置:

引用折叠

定义于头文件 <functional>  
template< class F, class... Args > /unspecified/ bind( F&& f, Args&&... args ); (C++11 起) (C++20 前)
template< class F, class... Args > constexpr /unspecified/ bind( F&& f, Args&&... args ); (C++20 起)
template< class R, class F, class... Args > /unspecified/ bind( F&& f, Args&&... args ); (C++11 起) (C++20 前)
template< class R, class F, class... Args > constexpr /unspecified/ bind( F&& f, Args&&... args ); (C++20 起)

函数模板 bind 生成 f 的转发调用包装器。调用此包装器等价于以一些绑定到 args 的参数调用 f

上面是cppreferencebind的声明形式,第一个参数看起来是一个右值引用,为什么在真正使用过程中,传入一些左值也可以呢?

这涉及到模板中的引用折叠,引用折叠是 C++11 引入的一个重要概念,主要用于处理模板参数推导完美转发中的引用类型组合问题。

当在模板中出现"引用的引用"时,C++ 编译器会根据特定规则将它们"折叠"成单一的引用类型。

引用折叠遵循以下四条规则:

只有当两个都是右值引用时,结果才是右值引用;否则都是左值引用。

引用包装器

bind的默认传递方式是值传递,如果在bind中想要使用引用传递,需要用到引用包装器

引用包装器是C++11引入的一个重要工具类,主要用于解决引用无法直接存储在容器中的问题。

std::reference_wrapper<T> 是一个类模板,它包装了对类型T对象的引用,使得引用可以像对象一样被复制、赋值和存储。

通过std::refstd::cref,我们可以在使用的时候直接创建引用包装器,从而在容器、算法和函数绑定中安全高效地使用引用,避免不必要的对象拷贝,提高程序性能。

在bind传参时可以使用std::ref,如果func函数中原本形参形式为const引用,相应地可以使用std::cref

bind的返回值

std::bind 的返回值是一个未指定类型的函数对象,这个类型是实现定义的,但它满足特定的接口要求。

std::bind 的返回值是:

  1. 实现定义的可调用类型: 具体类型由编译器决定

  2. 满足 Callable 概念: 可以使用 operator() 调用

  3. 可复制可移动: 支持标准的值语义

  4. 类型擦除友好: 可以存储在 std::function

  5. 支持完美转发: 保持参数的值类别

在现代C++中,通常使用 auto 接收 std::bind 的返回值,或者在需要类型擦除时使用 std::function。对于简单的绑定场景,Lambda表达式通常是更好的选择。

返回值类型特性

1. 未指定的可调用类型

2. 可调用对象 (Callable)

常见的接收方式

1. 使用auto

2. 使用 std::function

3. 模板参数推导

返回值的重要特性

1. 可复制和可移动

2. 支持完美转发

3. 嵌套绑定

std::function

std::function 是C++11引入的一个通用函数包装器,它可以存储、复制和调用任何可调用对象(函数、Lambda表达式、函数对象、成员函数等)。作为函数适配器,它提供了统一的接口来处理不同类型的可调用实体。

基本使用

作为函数适配器的应用

1. 统一不同类型的可调用对象

2. 回调函数系统

std::mem_fn成员函数适配器

std::mem_fn 是C++11引入的一个函数适配器,专门用于将成员函数转换为可调用的函数对象。它提供了一种简洁的方式来处理成员函数指针,使其能够与STL算法和其他需要函数对象的场景配合使用。