首页 > 代码库 > STL适配器(adapters)

STL适配器(adapters)

定义:将一个class的接口转换为另一个class的接口,使原本因接口不兼容而不能合作的classes,可以一起运作。适配器扮演者轴承、转换器的角色。

分类:
1、容器适配器:改变容器接口。
STL提供两个容器迭代器:queue和stack。它们都是修饰deque后成为另一种风貌的容器。

2、迭代器适配器:改变迭代器接口。
  • Insert Iterator:将容器绑定到back_insert_iterator、front_insert_iterator、insert_iterator。它们都是一个类,对它们的赋值操作将转换为对绑定容器的插入操作。为了操作方便,向用户提供的是一个函数,函数中才创建上述类。
以back_inserter为例:
template <class Container>
inline back_insert_iterator<Container> back_inserter(Container& x) {
  return back_insert_iterator<Container>(x);     // 以容器为参数,创建迭代器适配器
}


注意,一般迭代器适配器不会以迭代器作为参数,这里通过传入一个容器创建一个迭代器适配器。

下面是back_insert_iterator类的定义:
template <class Container>
class back_insert_iterator {
protected:
  Container* container; // 注意,这里是一个指向容器的指针
public:
  typedef output_iterator_tag iterator_category;     // 输出迭代器,只支持自增
  typedef void                value_type;
  typedef void                difference_type;
  typedef void                pointer;
  typedef void                reference;
 
  explicit back_insert_iterator(Container& x) : container(&x) {}     // 与容器相绑定
  back_insert_iterator<Container>&
  operator=(const typename Container::value_type& value) { 
    container->push_back(value);
    return *this;
  }
  back_insert_iterator<Container>& operator*() { return *this; }
  back_insert_iterator<Container>& operator++() { return *this; }
  back_insert_iterator<Container>& operator++(int) { return *this; }
};



可以看到,迭代器适配器提供了自增和接引用的接口,但是实际的功能被关闭了。上述代码的关键在于,对迭代器适配器的赋值变为了对容器的插入操作。

下面是我自己写的一个类似于上面的迭代器适配器:
#include <iostream>
#include <vector>
 
using namespace std;
 
template <class Container>
class my_back_insert_iterator {
protected:
  Container* container;
public:
  explicit my_back_insert_iterator(Container& x) : container(&x) {}
  my_back_insert_iterator<Container>&
  operator=(const typename Container::value_type& value) {
    container->push_back(value);
    return *this;
  }
};
 
int main()
{
    vector<int> vec;
    my_back_insert_iterator< vector<int> > back_insert(vec);
 
    back_insert = 1;
    back_insert = 2;
    back_insert = 3;
    back_insert = 4;
 
    vector<int>::iterator iter = vec.begin();
    for ( ; iter != vec.end(); ++iter)
        cout << *iter << endl;
 
    return 0;
}


运行结果:
对迭代器的赋值即是对容器的插入操作。

  • Reverse Iterator:使迭代器行进方向逆转。逆向迭代器一般用在容器中,容器都会提供一些接口,如下所示,使它可以和某些算法配合,逆向完成某些操作。
容器中的接口如下:
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }


在看一下reverse_iterator的构造函数:
Iterator current;     // 保存传入的迭代器
....
typedef Iterator iterator_type;
typedef reverse_iterator<Iterator> self;
explicit reverse_iterator(iterator_type x) : current(x) {}     // 构造函数


构造函数的任务就是把传入的迭代器保存在内部的current中。

下面是反向迭代器的关键所在:
reference operator*() const {
    Iterator tmp = current;
    return *--tmp;     // 先自减,再接引用
  }
 
  self& operator++() {
    --current;
    return *this;
  }
 
  self& operator--() {
    ++current;
    return *this;
  }


这里要注意的是接引用操作,先要把迭代器减1,这样才能保证对尾端元素的接引用是正确的。

测试代码如下:
int a[] = {9,5,4,8,3,6,7};
reverse_iterator<int*> reverite(a+7);
 
cout << *reverite++ << " ";
cout << *reverite++ << " ";
cout << *reverite++ << " ";


运行结果:

  • IOStream Iterator:将迭代器(istream_iterator或ostream_iterator)绑定到某个iostream对象(cin/cout)上,从而操作这些IO对象。
以绑定标准输出为例,测试代码如下:
int a[] = {9,5,4,8,3,6,7};
ostream_iterator<int> outite(cout, " ");     // 输出数组元素时附带输出空格
 
copy(a, a+7, outite);


运行结果:


上述代码将迭代器适配器绑定到标准输出,并且每输出一个元素,附带输出一个空格符。对迭代器的赋值(operator=)就是对它的输出操作(operator<<)。源代码如下:
template <class T>
class ostream_iterator {
protected:
  ostream* stream;     // 绑定的标准输出
  const char* string;  // 分隔符
public:
  typedef output_iterator_tag iterator_category;
  typedef void                value_type;
  typedef void                difference_type;
  typedef void                pointer;
  typedef void                reference;
 
  ostream_iterator(ostream& s) : stream(&s), string(0) {}
  ostream_iterator(ostream& s, const char* c) : stream(&s), string(c)  {}
  ostream_iterator<T>& operator=(const T& value) { 
    *stream << value;               // 关键点
    if (string) *stream << string;  // 输出间隔符
    return *this;
  }
// 以下三个操作都返回自己
  ostream_iterator<T>& operator*() { return *this; }
  ostream_iterator<T>& operator++() { return *this; } 
  ostream_iterator<T>& operator++(int) { return *this; } 
};


注意最后三个操作都只是本身,这样做的目的是可以配合copy()等算法:
*result = *first;
result就是该迭代器,对它接引用然后进行赋值操作将调用上面的operator=函数,使得copy()算法能够把一段范围内的元素输出到标准输出。这也正是泛型算法的精妙之处:算法只负责做固定工作,至于具体如何实现,由各个迭代器来完成。

3、函数适配器:改变仿函数/函数接口,用于特化和扩展一元和二元函数对象,使其能够适应STL算法的参 
      数接口。标准库将它分两类:
  • 绑定器(bind1st/bind2nd):将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
  • 求反器:将谓词函数对象的真值求反。
以count_if为例,应用程序调用如下代码:
// 计算容器中小于等于3的元素个数
cout << count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 3));


则会调用如下函数:
template <class InputIterator, class Predicate, class Size>
void count_if(InputIterator first, InputIterator last, Predicate pred,
              Size& n) {
  for ( ; first != last; ++first)
    if (pred(*first))       /* 对每个元素调用仿函数 */
      ++n;                  /* 满足条件则累加 */
}


下面看看pred,也就是bind2nd的源代码:
template <class Operation> 
class binder2nd
  : public unary_function<typename Operation::first_argument_type,
                          typename Operation::result_type> {
protected:
  Operation op;
  typename Operation::second_argument_type value;
public:
  binder2nd(const Operation& x,   // 仿函数
            const typename Operation::second_argument_type& y)  // 绑定的第二个数
      : op(x), value(y) {}
  typename Operation::result_type
  operator()(const typename Operation::first_argument_type& x) const {
    return op(x, value);  // 关键点
  }
};


代码一目了然。需要注意一点,此函数适配器必须要继承自unary_function对象,满足可配接性。解释一下可配接性。less_equal类继承自binary_function,便有了内部嵌套类型second_argument_type,而这个类型正好需要用在binder2nd中,以保存(绑定)某个参数。这样,less_equal就变为了可配接的。如果还有其它的适配器需要继续作用在binder2nd上,那么binder2nd也需要提供这样的嵌套类型,使它变为可配接的。这在另一篇文章“<ch07>仿函数”中也有所说明。

总结:
纵观整个适配器系统,基本上都是把某个对象或指向对象的指针封装在一个适配器类中,对适配器的操作最终都会传递到对所包含对象的操作上来。

参考:
《C++ primer》 P453.
《STL源码剖析》 第八章。