这部分开始适配器的讲解,讲适配器之前还讲了仿函数,但由于比较简单,所以这里简单提一下仿函数。
仿函数就是具有函数特性的class,主要是为算法服务的。STL中有三类仿函数,算术类、逻辑类和相对关系类,下面举几个例子:
//算术类
template <class T>
struct plus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const
{ return x + y; }
};
template <class T>
struct minus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const
{ return x - y; }
};
//逻辑运算类
template <class T>
struct logical_and : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const
{ return x && y; }
};
//相对关系类
template <class T>
struct equal_to : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const
{ return x == y; }
};
template <class T>
struct less : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const
{ return x < y; }
};
可以看出STL中的仿函数比较特别的就是继承了binary_function,(有的是继承unary_function),这其实就是“融入”STL的条件。我们把类似继承了binary_function的仿函数叫做“可适配的仿函数”,binary_function其实只是赋予了仿函数一堆typedef,当适配一个仿函数的时候,适配器会去“问”仿函数一些问题,只有继承了这些typedef的仿函数能够回答这些问题,后面正式将适配器我们会看到这些例子。
目录
一、容器适配器
之前提到过的stack,queue都是容器适配器,都是内含一个真正“做事”的容器,如下图所示:

二、函数适配器
1. bind2nd
bind2nd就是一种仿函数适配器,功能是将仿函数的第二个参数绑定为特定值,源码如下:
template <class Operation, class T>
inline binder2nd<Operation> bind2nd(const Operation& op, const T& x){
typedef typename Operation::second_argument_type arg2_type;
return binder2nd<Operation>(op, arg2_type(x)); //这里做类型转换是为了对传入参数进行检查
}
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);
}
};
可以看出bind2nd是把工作交给了binder2nd,binder2nd实际上就是一个接受一个参数的仿函数,从它继承了unary_function也能看出来,它把传入的第二参数作为成员,当我们调用这个接受一个参数的适配器时,它里面就会调用原先两个参数的仿函数,并传入保存的第二参数的值。除此之外,能看出里面还有大量的typename,只有能回答这些问题的仿函数才是可适配的,也就是得继承binary_function或者unary_function。
2. not1
又一个仿函数适配器的例子,源码如下:
template <class Predicate>
inline unary_negate<Predicate> not1(const Predicate& pred){
return unary_negate<Predicate>(pred);
}
template <class Predicate>
class unary_negate
: public unary_function<typename Predicate::argument_type, bool>{
protected:
Predicate pred;
public:
explicit unary_negate(const Predicate& x) : pred(x) {}
bool operator()(const typename Predicate::argument_type& x) const{
return !pred(x);
}
};
3. bind
自C++11以来,bind1st,bind2nd,binder1st,binder2nd这些其实都已经被一个叫bind的函数所取代,下面介绍一下bind的用法。bind可以绑定四种东西:(1)函数和仿函数;(2)类的数据成员以及成员函数,本质上都是类的成员,其中_1必须是某个object的地址。
(1) 下面是第一类用法的示例:
double my_divide (double x, double y)
{ return x / y; }
//bind的使用示例
auto fn_five = bind(my_divide, 10, 2); //fn_five() return 10/2
auto fn_half = bind(my_divide, _1, 2); //fn_half(x) return x/2
auto fn_invert = bind(my_divide, _2, _1); //fn_invert(x, y) return y/x
auto fn_rounding = bind<int> (my_divide, _1, _2); //fn_rounding(x, y) return (int)x/y
形如_n的东西叫做占位符,意思是实际调用的第n个参数,这个_n在哪个位置,实际调用的那个参数就在哪个位置。具体见例子
(2) 下面是第二类用法的示例:
struct MyPair{
double a, b;
double multiply() { return a * b; }
//member function其实有个argument:this
};
//binding类的成员的示例
MyPair ten_two {10, 2};
auto bound_memfn = bind(&MyPair::multiply(), _1);
//bound_memfn(x) return x.multiply()
auto bound_memdata = bind(&MyPair::a, ten_two);
//bound_memdata() return tem_two.a
auto bound_memdata2 = bind(&MyPair::b, _1);
//bound_memdata2(x) return x.b
可以看出bind可以实现绑定类的成员,无论是数据成员还是函数成员,bind需要传入成员指针,它的_1始终表示对象的this指针,当不指定具体传入的对象时,调用bind后的结果时就需要传入一个object,有点像一个隐藏参数。
三、迭代器适配器
1. reverse_iterator
逆向迭代器原理如下图所示:

可以看出逆向迭代器需要满足两个行为:(1)正确地引用元素值。(2)方向和原来相反。以rbegin()为例,它的值按理来说和end()是完全相同的,但是指向元素的含义却不同。以下是逆向迭代器的源码,从运算符重载函数可以看出以上两个行为的体现:
reverse_iterator
rbegin()
{ return reverse_iterator(end()); }
reverse_iterator
rend()
{ return reverse_iterator(begin()); }
template <class Iterator>
class reverse_iterator
{
protected:
Iterator current; //对应的正向迭代器
publlic:
typedef typename iterator_traits<Iterator>::iterator_category iterator_category;
typedef typename iterator_traits<Iterator>::value_type value_type;
......
//除以上5个迭代器必须的typedef之外,还有两个特有的
typedef Iterator iterator_type; //代表正向迭代器
typedef reverse_iterator<Iterator> self; //代表逆向迭代器
public:
explicit reverse_iterator(iterator_type x): current(x) {}
reverse_iterator(const self& x): current(x.current) {}
iterator_type base() const { return current; }
reference operator* const { Iterator tmp = current; return *--tmp; }
//体现了第一个行为
pointer operator->() const { return &(operator*()); }
//体现了第二个行为
self& operator++() { --current; return *this; }
self& operator--() { ++current; return *this; }
self operator+(difference_type n) const { return self(current - n); }
self operator-(difference_type n) const { return self(current + n); }
};
2. inserter
我们知道标准库的copy()函数格式为:copy( begin(), end(), result() ),将会在目的端进行元素的拷贝赋值,那么如果我们想在目的端做插入赋值该怎么办?答案是使用inserter(),所以copy()函数就可以写成这样:
copy( begin(), end(), inserter(container, result()) )
那么inserter是怎么做到这一点的呢?答案是inserter重载了operator=(),这样就改变了copy()中使用的赋值运算符的含义,源码如下:
template <class Container, class Iterator>
inline insert_iterator<Container>
inserter(Container& x, Iterator i){
typedef typename Container::iterator iter;
return insert_iterator<Container>(x, iter(i));
}
template <class Container>
class insert_iterator{
protected:
Container* container;
typename Container::iterator iter;
public:
typedef output_iterator_tag iterator_category;
insert_iterator(Container& x, typename Container::iterator i)
: container(&x), iter(i) {}
insert_iterator<Container>&
operator=(const typename Container::value_type& value){
iter = container->insert(iter, value);
++iter;
return *this;
}
};
四、ostream和istream的适配器
这里介绍ostream_iterator和istream_iterator,这两个虽然叫做iterator,但是和传统的迭代器有很大的不同,它并不是那种指向容器里的一个元素,自己还可以移动,解引用之类的东西,所以把这两个东西定性为ostream和istream的适配器。
1. ostream_iterator
通过源码来示范一下它的用法:
std::vector<int> myvector = {1, 2, 3, 4, 5, 6, 7, 8, 9};
std::ostream_iterator<int> out_it(std::cout, ",");
std::copy( myvector.begin(), myvector.end(), out_it);
//ostream_iterator的源码
template <class T, class charT = char, class traits = char_traits<charT>>
class ostream_iterator :
public iterator<output_iterator_tag, void, void, void, void>
{
basic_ostream<charT, traits>* out_stream;
const charT* delim;
public:
typedef charT char_type;
typedef traits traits_type;
typedef basic_ostream<charT, traits> ostream_type;
//构造函数
ostream_iterator(ostream_type& s): out_stream(&s), delim(0) {}
ostream_iterator(ostream_type& s, const charT* delimiter)
:out_stream(&s), delim(delimiter) {}
ostream_iterator(const ostream_iterator<T, charT, traits>& x)
:out_stream(x.out_stream), delim(x.delim) {}
~ostream_iterator() {}
//运算符重载
ostream_iterator<T, charT, traits>& operator=(const T& value){
*out_stream << value;
if(delim != 0) *out_stream << delim;
return *this;
}
ostream_iterator<T, charT, traits>& operator*() { return *this; }
ostream_iterator<T, charT, traits>& operator++() { return *this; }
ostream_iterator<T, charT, traits>& operator++(int) { return *this; }
};
观察构造函数可以看出,ostream_iterator可以接受一个basic_ostream类型和一个分隔符,通过对operator=的重载,将赋值运算符定义为了向对应的basic_ostream中放东西,由此来达到输出值的效果。具体见构造函数和运算符重载函数。
还有一个值得注意的是:operator*(),operator++()和operator++(int)什么都不做,和传统意义上的迭代器的操作有根本上的不同。
2. istream_iterator
下面来通过源码看istream_iterator的使用:
istream_iterator<int> iit(cin), eos;
copy(iit, eos, inserter(c, c.begin()));
//istream_iterator的源码
template <class T, class charT = char, class traits = char_traits<charT>,
class Distance = ptrdiff_t>
class istream_iterator:
public iterator<input_iterator_tag, T, Distance, const T*, const T&>
{
basic_istream<charT, traits>* in_stream;
T value;
public:
typedef charT char_type;
typedef traits traits_type;
typedef basic_istream<charT, traits> istream_type;
istream_iterator() : in_stream(0) {}
istream_iterator(istream_type& s) : in_stream(&s) { ++*this; }
istream_iterator(const istream_iterator<T, charT, traits, Distance>& x)
:in_stream(x.in_stream), value(x.value) {}
~istream_iterator() {}
const T& operator*() const { return value; }
const T* operator->() const { return &value; }
istream_iterator<T, charT, traits, Distance>& operator++(){
if(in_stream && !(*in_stream >> value)) in_stream = 0;
return *this;
}
istream_iterator<T, charT, traits, Distance> operator++(int){
istream_iterator<T, charT, traits, Distance> tmp = *this;
++*this;
return tmp;
}
};
可以看出istream_iterator是通过重载operator++()来输入元素的,并且要注意的是在创建一个非空迭代器的同时,就会调用operator++(),所以会产生等待输入的情况。创建一个没有参数的迭代器时,其内部的指针in_stream会被置为0,用来标志输入的结束。
本文深入解析STL中的各种适配器,包括容器适配器如stack和queue,函数适配器如bind2nd和not1,迭代器适配器如reverse_iterator和inserter,以及ostream和istream的适配器。通过源码分析,揭示适配器如何改变STL组件的行为。

888

被折叠的 条评论
为什么被折叠?



