STL——配接器(adapters)
一、配接器
《Design Patterns》一书提到23个最普及的设计模式,其中对adapter样式的定义如下:将一个class的接口转换为另一个class 的接口,使原本因接口不兼容而不能合作的classes,可以一起运作。
1. 配接器概观与分类
STL 所提供的各种配接器中,改变仿函数接口者,我们成为function adapter;改变容器接口者,我们称为container adapter;改变迭代器接口者,我们称为iterator adapter。
1.1 应用于容器——container adapter
STL提供的两个容器queue和stack,其实都只不过是一种配接器,它们修饰deque 的接口而成就出另一种容器风貌。
1.2 应用于迭代器——iterator adapter
STL提供了许多应用于迭代器身上的配接器,包括insert iterators, reverse iterators, iostream iterators。C++ Standard 规定它们的接口可以藉由<iterator> 获得,SGI STL 则将它们实际定义于<stl_iterator.h>.
(1)Insert Iterators
所谓Insert Iterators,可以将一般迭代器的赋值操作转变为插入操作。这样的迭代器包括专司尾端插入操作的back_insert_iterator,专司头端插入操作的front_insert_iterator,以及可从任意位置执行插入操作的insert_iterator。由于这三个iterator adapters 的使用接口不是十分直观,给一般用户带来困扰,因此,STL 更提供了三个相应函数:back_inserter(), front_inserter(), inserter(), 提升使用时的便利性。
(2)Reverse Iterators
所谓reverse iterators ,可以将一般迭代器的行进方向逆转,使原本应该前进的operator++变成后退操作,使原本应该后退的operator--变成前进操作。
(3)IOStream Iterators
所谓iostream iterators 可以将迭代器绑定到某个iostream对象身上。例如,绑定到istream对象(如std::cin)身上的,称为istream_iterator,拥有输入功能;也可以绑定到ostream对象身上。
1.3 应用于仿函数——functor adapters
functor adapters,或叫做function adapters,是所有配接器中数量最庞大的一个族群,其配接灵活度也是前二者所不能及,可以配接,配接,再配接。这些配接操作包括绑定(bind),否定(negate),组合(compose),以及对一般函数或成员函数的修饰(使其成为一个仿函数)。C++ Standard 规定这些配接器的接口可由<functional>获得,SGI STL 则将它们实际定义于<stl_function.h>。
function adapters的价值在于,通过它们之间的绑定、组合、修饰能力,几乎可以无限制地创造出各种可能的表达式,搭配STL算法一起演出。例如,我们可能希望找出某个序列中所有不小于12 的元素个数,那么可以这么做:
not1(bind2nd(less<int>(), )); //(1) less<int>(), 创建一个匿名的临时对象(同时也是仿函数)
又如:我们希望将容器内的每一个元素v进行(v+2)*3 的操作,我们可以令f(x) = x * 3, g(y) = y + 2;并 写下这样的式子:
compose1(bind2nd(multiplies<int>(), ), bind2nd(plus<int>(), )); // (2)第一个参数被拿来当做f(), 第二个参数被拿来当做g(). // f(g(elem)); 可以写成:compose1(f(x), g(y));
上面(2)表达式,可以拿来和任何接受表达式的算法搭配。不过,务请注意,这个算式会改变参数的值,所以不能和non-mutating算法搭配。例如不能和for_each搭配,但可以和transform搭配,将结果输往另一地点。
请注意,所有期望获得配接能力的组件,本身都必须是可配接的(adaptable)。换句话说,一元仿函数必须继承自unary_function,二元仿函数必须继承自binary_function,成员函数必须以mem_fun处理过,一般函数必须以ptr_fun处理过。一个未经ptr_fun处理过的一般函数,虽然也可以函数指针(pointer to function)的形式传给STL算法使用,却无法拥有任何配接能力。
图8-2是STL function adapters一览表
2. container adapters
stack和queue,参见相关源码
3. Iterator adapters
3.1. insert iterators
insert iterators 的实现中,其中的主要观念是,每一个insert iterators 内部都维护有一个容器(必须由用户指定);容器当然有自己的迭代器,于是,当客户端对insert iterators做赋值操作时,就在insert iterators中被转为对该容器的迭代器做插入操作,也就是说,在insert iterators的operator=操作符中调用底层容器的push_front()或push_back()或insert()操作函数。至于其他的迭代器惯常行为如 operator++, operator++(int), operator* 都被关闭功能,更没有提供 operator--(int) 或 operator--或 operator->等功能(因此类型被定义为output_iterator_tag)。换句话说,insert iterators的前进、后退、取值、成员取用等操作都是没有意义的,甚至是不允许的。 参见相关源码;
3.2. reverse iterators
reverse iterator, 就是将迭代器的移动行为逆转。只要是双向序列容器提供了begin(), end() ,它的rbegin(), rend() 就是使用reverse iterator。单向序列容器slist 不可使用reverse iterators。有些容器如stack,queue,priority_queue并不提供begin(), end(),当然也就没有rbegin(), rend()。 当迭代器被逆转,虽然实体位置不变,但逻辑位置必须改变。逆转时,会为了配合迭代器区间的“前闭后开”的特性作相应的调整。 参见相关源码;
3.3. stream iterators
stream iterators,可以将迭代器绑定到一个stream(数据流)对象身上。绑定到istream对象(如std::cin)者,称为istream_iterator,拥有输入能力;绑定到ostream对象(如std::cout)者,称为ostream_iterator,拥有输出能力。 参见相关源码;
4. function adapters
一般而言,对于C++ template 语法有了某种程度的了解之后,我们很能够理解或想象,容器是以class template 完成的,算法以function template 完成,仿函数是一种将 operator() 重载的 class template,迭代器则是一种将 operator++ 和 operator* 等指针惯常行为重载的class template。然而配接器呢?应用于容器身上和迭代器身上的配接器,已于上面介绍过,都是一种 class template。可应用于仿函数身上的配接器呢?如果能够“事先”对一个函数完成参数的绑定、执行结果的否定、甚至多方函数的组合?请注意我用“事先”一词。我的意思是,最后修饰结果(视为一个表达式,expression)将被传给STL算法使用,STL算法才是真正使用这个表达式的主格。而我们都知道,只有在真正使用(调用)某个函数(或仿函数)时,才有可能对参数和执行结果做任何干涉。
那么仿函数怎么实现对参数和返回值的干涉呢?关键在于,就像本章先前所揭示,container adapters 内藏了一个container memeber 一样,或是像reverse iterator(adapters)内藏了一个pointer to stream一样,或是像insert iterator(adapters)内藏了一个pointer to container(并因而得以取其iterator)一样,每一个function adapters 也内藏了一个member object,其型别等同于它所要配接的对象(那个对象当然是一个“可配接的仿函数”,adaptable functor),下图8-6是一份整理。当function adapter 有了完全属于自己的一份修饰对象(的副本)在手的时候,它就成了该修饰对象(的副本)的主人,也就有资格调用该修饰对象(一个仿函数),并在参数和返回值上面动手脚了。
binder2nd 中,构造函数中的x参数是仿函数;operator() 运算子中的参数x是 当运算子operator() 被调用时 由调用者传递进来的第一个参数。
4.1 对返回值进行逻辑否定:
not1, not2 参见相关源码;
4.2 对参数进行绑定:bind1st,bind2nd 参见相关源码;
4.3 用于函数合成:compose1, compose2
compose1,对两个可配接函数f(), g(), 产生一个h(), 使 h(x) = f(g(x));
compose2,对三个可配接函数f(), g1(), g2(), 产生一个h(), 使 h(x) = f(g1(x), g2(x));
参见相关源码;
4.4 用于函数指针:ptr_fun 参见相关源码;
4.5 用于成员函数指针:mem_fun, mem_fun_ref
这种配接器使我们能够将成员函数当做仿函数来使用,于是成员函数可以搭配各种泛型算法。当容器的元素型式是X& 或X* ,而我们又以虚拟成员函数作为仿函数,便可以藉由泛型算法完成所谓的多态调用。这是泛型与多态之间的一个重要接轨。
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional> using namespace std; class Shape
{
public:
virtual void display() = ;
}; class Rect : public Shape
{
public:
virtual void display() { cout << "Rect"; };
};
class Circle : public Shape
{
public:
virtual void display() { cout << "Circle"; };
};
class Square : public Rect
{
public:
virtual void display() { cout << "Square"; };
}; int main()
{
// 容器只支持对象语义,不支持引用语义
vector<Shape*> V;
V.push_back(new Rect);
V.push_back(new Circle);
V.push_back(new Square);
V.push_back(new Circle);
V.push_back(new Rect); // 多态调用(polymorphically)
for(int i = ; i < V.size(); ++i)
(V[i])->display();
cout << endl; // Rect Circle Square Circle Rect //
for_each(V.begin(), V.end(), mem_fun(&Shape::display));
cout << endl; // Rect Circle Square Circle Rect
}
(1)请注意,就语法而言,你不能写:
for_each(V.begin(), V.end(), &Shape::display);
也不能写:
for_each(V.begin(), V.end(), Shape::display);
一定要以配接器mem_fun 修饰 member function,才能被算法for_each接受。
(2)另一个必须注意的是,虽然多态可以对pointer或reference起作用,但很可惜的是,STL容器只支持“实值语意”,不支持“引用语意”,因此下面这样无法通过编译: vector<Shape&> V; 参见STL概论 提示17-9;
STL——配接器(adapters)的更多相关文章
- STL 配接器(adapters)
定义 配接器(adapters):将一个class的接口,转换为另一个class的接口,使得原来不能一起使用相互兼容的classes,可以一起协同工作. 配接器是一种设计模式. STL中提供的各种配接 ...
- STL——配接器、常用算法使用
学习STL,必然会用到它里面的适配器和一些常用的算法.它们都是STL中的重要组成部分. 适配器 在STL里可以用一些容器适配得到适配器.例如其中的stack和queue就是由双端队列deque容器适配 ...
- STL容器与配接器
STL容器包括顺序容器.关联容器.无序关联容器 STL配接器包括容器配接器.函数配接器 顺序容器: vector 行为类似于数组,但可以根据要求 ...
- 《STL源代码剖析》学习笔记系列之七、八——仿函数和配接器
1. 仿函数 仿函数又名函数对象.具有函数性质的对象.就是传入一些參数.然后对參数进行某些运算,然后返回一个值. 为了可以使行为类似函数,须要在类别定义中必须自己定义function call 运算子 ...
- STL源码剖析:配接器
启 配接器就是适配器 STL中的适配器一共三种: 迭代器适配器 是一种观念上的改变,如将赋值操作变成插入,前进变成后退,等 函数适配器 STL中最广泛的配接器群体 可以实现连续配接 配接操作:bind ...
- STL学习笔记(迭代器配接器)
Reverse(逆向)迭代器 Reverse迭代器是一种配接器. 重新定义递增运算和递减运算.使其行为正好倒置. 如果你使用这类迭代器,算法将以逆向次序处理元素.所有标准容器都允许使用Reverse迭 ...
- STL内存配置器
一.STL内存配置器的总体设计结构 1.两级内存配置器:SGI-STL中设计了两级的内存配置器,主要用于不同大小的内存分配需求,当需要分配的内存大小大于128bytes时, 使用第一级配置器,否则使用 ...
- 【陪你系列】5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码
大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub https://github.com/rongweihe/MoreT ...
- STL空间配置器源码分析(四)bitmap_allocator
一.摘要 bitmap_allocator是STL空间分配器的其中一种,它采用内存池策略,最多存储64条空闲链表(freelist,实际是一块空间连续的内存区,后面也称为超级块),每条空闲链表存储的内 ...
随机推荐
- Selenium常用操作汇总二——如何处理alert、confirm、prompt对话框
alert.confirm.prompt这样的js对话框在selenium1.X时代也是难啃的骨头,常常要用autoit来帮助处理. 试用了一下selenium webdriver中处理这些对话框十分 ...
- MD5骨骼动画模型加载(一)
前面我们分析了静态模型OBJ格式,桢动画模型MD2,这篇主要分析骨骼动画MD5的一些概念并且实现. 混合桢动画有计算简单,容易实现等优点,但是在需要比较细致的效果时,则需要更多的关键桢,每桢都添加相同 ...
- (笔记)Mysql命令delete from:删除记录
delete from命令用于删除表中的数据. delete from命令格式:delete from 表名 where 表达式 例如,删除表 MyClass中编号为1 的记录: mysql&g ...
- Java如何比较两个数组?
在Java中,如何比较两个数组? 示例 以下示例使用equals方法来检查两个数组是否相等. package com.yiibai; import java.util.*; public class ...
- 解决ubuntu上网慢的方法
在ubuntu下用firefox等浏览器上网,往往比在windows下上网要慢好多,但细心的人会发现,慢的时间是花在DNS查找上面了.那么 我们可以在本机缓存DNS,也就是在本机架设一个DNS代理服务 ...
- asp.net mvc用aspose.cells 导出xlsx格式的excel。无残留
public void Export() { HttpResponse Response = System.Web.HttpContext.Current.Response; // Load your ...
- 9款赏心悦目的HTML5/CSS3应用特效
经过几天的收集,在html5tricks网站上又增加了不少HTML5的教程和应用,今天我把几款赏心悦目的HTML5/CSS3应用特效总结了一下,一共9款HTML5特效,分享给大家. 1.HTML5 W ...
- Android学习之——ListView下拉刷新
背景知识 ListView使用非常广泛,对于使用ListView的应用来说,下拉刷新是必不可少要实现的功能. 我们常用的微博.网易新闻,搜狐新闻都使用了这一功能,如下图所示. 微博 搜狐新闻 ...
- windows下redis启动失败提示maxheap flag
windows下redis启动失败 D:\redis>redis-server.exe redis.conf [] Oct ::39.789 # The Windows version of R ...
- web front end stack web 前段技术概览
https://github.com/unruledboy/WebFrontEndStack