0
点赞
收藏
分享

微信扫一扫

适配器底层源码解析及实现——STL源码剖析第八章的总结回顾

什么是适配器

        适配器(Adaptor)也叫做包装器, 是C++的STL六大组件之一(容器(Container)、迭代器(Iterator)、仿函数(Function)、算法(Algorithm)、适配器(Adaptor)、分离器(Allocator))

        C++的STL全名Standard Template Library, 即“标准模板库”。里面是有着高强的复用性的、类型极多的不同类型的各种组件。这些组件就像一个机器上面的一个个轴承, 弹簧等零件一样。 可以相互配接, 相互组合。但又互不影响。(这也是封装的体现所在, 封装降低了代码的耦合度——降低耦合;通过屏蔽掉外部代码的对内部的影响,维护了代码块里面的数据的完整度, 安全性——增加安全。 同时又能屏蔽掉内部的代码对外部的一些影响,只暴露一部分操作在外面供人们使用——改变接口;使代码模块化, 好维护, 易复用——易于复用与维护。)

        在C++的STL中, 迭代器, 容器, 算法, 仿函数分别都是一类独特的组件。 其中, STL想要将容器和算法独立开来。 并且迭代器作为他们之间的胶合剂, 仿函数用来配合算法,并且具有很强配接性。  而包装器, 也就是适配器也可以用来配接仿函数。

        对于适配器来说, 主要分为三种适配器——Iterator Adaptor、Function Adaptor、Container Adaptor;下面开始对这三种适配器的使用进行举例

应用举例

Container Adaptor

        容器适配器就是用来封装容器的容器。那么, 请想一下, 在stl容器里面。 哪个容器可以封装别的容器呢? 

        在stl容器里面,queue, stack, priority_queue都是用来封装其他容器的容器。

ps:这里只是举例, 只需要知道这三个容器时用来封装容器的容器, 也就是容器适配器即可。 后面将会手撕代码展现底层, 那时候就可以深入的了解为什么他们是封装容器的容器。  

Iterator Adaptor

Insert Iterator

        insert iterator是一个统称。 这里面其实有三个迭代器: 代表从头部插入的front_inserter_iterator<Container>、代表从尾部插入的back_inserter_iterator<Container>、代表从随机位置插入的inserter_iterator<Container>;

        为了更好的使用这三个迭代器, stl又提供了三个相应的函数: front_inserter<Container>对应front_inserter_iterator<Container>、 back_inserter<Container>对应back_inserter_iterator<Container>、Inserter<Container>对应inserter_iterator<Container>;

        这三个迭代器的用法如下:(注意, 这三个迭代器只能用于容器的插入操作, 其实从模板里面也能看出来, 我上面写的无论是函数还是迭代器类, 他们的模板参数都是Container。 也就是容器, 说明他们是用于给任意容器进行适配的——前提是这个容器有头插或者尾插或者插入

Reverse iterator

        Reverse iterator翻译过来就是反向迭代器。我们打开c++list库的文档就可以看到, list是有反向迭代器的:

        不仅list, vector, deque也都有着反向迭代器。

        反向迭代器的加加其实就是正常容器迭代器的减减,而反向迭代器的减减其实就是正常迭代器的加加;

        至于为什么会这样, 后面会手撕Reverse iterator来深入解析如何产生这种效果。

stream iterator

        IOstream iterator是用来封装流的适配器。 他能够将流的对象进行封装, 然后进行统一处理。  IOstream iterator分为 : 代表流插入的osteram iterator 、 代表流提取的istream iterator。

以上, IOstream iterator的大概用法, 后面会手撕代码来深入解析底层原理。

Function Adaptor

先看应用:

ptr系列 

以上, 即Function Adaptor常见适配器的用法。接下来手撕代码, 探究底层原理.

Container Adaptor

        容器适配器有queue, stack, priority_queue. 这里首先看queue

queue

queue的模板参数:

	template<class Container>  //要包装的容器类型。
class queue

queue的保存的数据类型如何解决?

         第一种解决方法是在queue的模板参数中再加一个参数类型, 也就是:

	template<class T, class Container>//第一个模板参数是存储的数据类型, 第二个模板参数是封装的容器类型
class queue

        第二种方法就是就是萃取traits):直接使用typename在指定类型后再进行识别要储存到类型。 但是typename需要知道我们要封装的容器Container里面要储存的数据类型——这里就用到了STL的强大的复用性。 在STL中, 每一个容器要储存的类型名称都叫做value_type。下面尾vs中的标准库:

        所以,第二种方式如下:

template<class Container>
class queue
{
typedef typename Container::value_type value_type;
//…………其他成员函数
}

        知道了这些, 我们就可以着手完善我们的queue库了。

因为我们是要对Container容器进行封装。 所以要创建Container对象。 那么就是:

//容器适配器queue, 模板参数为Container, 意思是传一个容器的类型过来
template<class Container>
class queue
{
typedef typename Container::value_type value_type;
public:

private:
Container _con; //根据容器的类型创建一个对象
};

        那么要将Container封装成queue, 就要保留Container的尾差:push_bac()、头删:pop_front()、取对头元素:front()、取队尾元素:back()以及empty, size, capacity这些接口。 但是要封住它的迭代器等接口(对于非默认成员函数不定义即可, 但是对于默认成员函数如果要封住就要加delete。 对于这个queue类来说, 没必要封住默认成员函数

        其他接口的定义实现:

    //容器适配器queue, 模板参数为Container, 意思是传一个容器的类型过来
template<class Container>
class queue
{
typedef typename Container::value_type value_type;
public:
//conductor
queue() {}
queue(const queue<Container>& q)
:_con(q._con)
{}
~queue() {}

//modify
void push(const value_type& x)
{
//queue的push在尾部入队列。
_con.push_back(x);
}

void pop()
{
//queue的pop去掉头部的元素, 就是调用容器的头删。
_con.pop_front();
}

value_type& front()
{
return _con.front();
}

value_type& back()
{
return _con.back();
}

//capacity
bool empty()
{
return _con.empty();
}

size_t size()
{
_con.size();
}

size_t capacity()
{
return _con.capacity();
}

private:
Container _con; //根据容器的类型创建一个对象
};

       stack

stack的实现思想基本和queue的一样。都是一个模版参数Container, 然后利用这个模版参数实例化出对象。 同时使用一下Container里面的vatue_type。如下代码:


template<class Container>
class stack
{
typedef typename Container::value_type _Ty;
public:

private:
Container _con;
};

        然后要提供压栈:push()就是调用Container的push_back()。 出栈就是Container的pop_back()。以及栈顶就是back()、empty、size、capacity等等。

        如下代码实现:

	template<class Container>
class stack
{
typedef typename Container::value_type _Ty;
public:
//conductor
stack() {};
stack(const stack<Container>& st)
:_con(st._con)
{}
~stack() {}

//modify
void push(const _Ty& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}

_Ty top()
{
return _con.back();
}

//capacity
bool empty()
{
return _con.empty();
}

size_t size()
{
return _con.size();
}

size_t capacity()
{
return _con.capacity();
}

private:
Container _con;
};

priority_queue

优先级队列和上面两个的参数略有不同。 因为优先级队列的类型不是单一的。 有大根堆还有小根堆之分。 而要分开大根还是小根, 使用的是一个模版参数Compare, 这个Compare是个仿函数模版,利用仿函数控制向上调整算法(这里博主默认友友们熟悉向上调整算法)比较逻辑。 就可以控制大根堆还是小根堆。 那么如何控制的?请看代码:


template<class T, class Container, class Compare = less<T>>
class priority_queue
{
public:


protected:
//向上调整算法
void adjust_up()
{
Compare com; //Compare 创建一个可调用对象。
int child = _con.size() - 1;
int parent = (child - 1) / 2;
//
while (child > 0)
{
//小堆是大于, 孩子小上去
if (com(_con[parent], _con[child])) //利用可调用对象来实行判断逻辑。
{
swap(_con[parent], _con[child]);
child = parent;
parent = (parent - 1) / 2;
}
else break;
}
}

private:
Container _con;
};

向下调整算法用的是同样的方法, 都是利用仿函数模板的实例对象实行判断逻辑。

		void adjust_down(int i) 
{
Compare com;
int parent = i;
int child = parent * 2 + 1;
int n = _con.size();

while (child < n)
{
//大堆是小于, 孩子大上去
if (child + 1 < n && com(_con[child], _con[child + 1])) ++child;
if (_con[parent], _con[child])
{
swap(_con[parent], _con[child]);
parent = child;
child = child * 2 + 1;
}
else break;
}
}

知道了这些, 就可以着实现代码:

	template<class T, class Container, class Compare = less<T>>
class priority_queue
{
public:
//conductor
priority_queue() {}
priority_queue(const priority_queue<T, Container, Compare>& pq)
:_con(pq._con)
{}
~priority_queue() {}

//modify
void push(const T& x)
{
//标准的堆插入操作, 先尾差, 再向上调整
_con.push_back(x);
adjust_up();
}

void pop()
{
//标准的堆
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}

T& top()
{
return _con[0];
}

//capacity
bool empty()
{
_con.empty();
}

size_t size()
{
return _con.size();
}

size_t capacity()
{
return _con.capacity();
}

protected:
//向上调整算法
void adjust_up()
{
Compare com;
int child = _con.size() - 1;
int parent = (child - 1) / 2;
//
while (child > 0)
{
//小堆是大于, 孩子小上去
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (parent - 1) / 2;
}
else break;
}
}

//向下调整算法, 从基准点开始, 所以要有一个基准点
void adjust_down(int i)
{
Compare com;
int parent = i;
int child = parent * 2 + 1;
int n = _con.size();

while (child < n)
{
//小于是大堆, 孩子大上去
if (child + 1 < n && com(_con[child], _con[child + 1])) ++child;
if (_con[parent], _con[child])
{
swap(_con[parent], _con[child]);
parent = child;
child = child * 2 + 1;
}
else break;
}
}


private:
Container _con;
};

Iterator Adaptor 

        ps:在本节内容中, 所实现的迭代器适配器之中都有五个特性: iterator_category、 value_type、difference_type、pointer、reference;这五个类型是为了让我们写的迭代器操作能够胶合算法和容器, 并且融于STL之中, 并不是本节适配器的重点内容。 如果有兴趣, 请看侯捷老师的STL源码剖析-p85-Traits编程技法

inserter Iterator

front_inserter_iterator

        想要使用front_Inserter需要容器含有push_front接口, 而vector没有这个接口。 那么vector就无法使用front_inserter。

	template<class Container>
class front_inserter_iterator
{
//迭代器的五个特性。 分别是:迭代的分类、迭代器的类型、迭代器之间的距离, 也是容器的最大容量、迭代器指向的对象、迭代器引用的对象;五个特性为迭代器相关内容,主要功能是为了胶合算法与容器。
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;

public:
//conductor传一个容器对象
front_inserter_iterator(Container& con)
:_con(&con)
{}

//赋值操作就是Container容器的头插操作。
front_inserter_iterator<Container> operator=(typename Container::value_type x)
{
_con->push_front(x);
return *this;
}
//解引用和前置++, 后置++都是它本身。
front_inserter_iterator<Container> operator*() { return *this; }
front_inserter_iterator<Container> operator++() { return *this; };
front_inserter_iterator<Container> operator++(int) { return *this; }

private:
Container* _con;
};


//为了方便使用front_inserter_iterator而设计的函数模板。 只需要传容器过去即可
template<class Container>
front_inserter_iterator<Container> front_inserter(typename Container& x)
{
return front_inserter_iterator<Container>(x); //构造一个front_inserter_iterator进行头插
}

back_inserter_iterator

	template<class Container>
class back_inserter_iterator
{
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void defference_type;
typedef void reference;
typedef void pointer;

public:
//conductor
back_inserter_iterator(Container& con)
:_con(&con)
{}

//返回值,赋值的操作其实就是尾插。和front_inserter_iterator是一样的
back_inserter_iterator<Container>& operator=(typename Container::value_type x)
{
_con->push_back(x);
return *this;
}

//解引用和前置++以及后置++都是返回它本身
back_inserter_iterator<Container>& operator*() { return *this; }
back_inserter_iterator<Container>& operator++() { return *this; }
back_inserter_iterator<Container>& operator++(int) { return *this; }

private:
Container* _con;
};

//辅助函数, 方便使用
template<class Container>
back_inserter_iterator<Container>& back_inserter(Container& x)
{
return back_inserter_iterator<Container>(x);
}

inserter_iterator

        随机位置插入和上面两个迭代器有所不同, 随机位置需要用到容器的迭代器。 这也就意味着queue, stack这些没有迭代器的直接无法使用inserter。

	template<class Container>
class inserter_iterator
{
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
public:
//inserter的构建需要传两个参数, 一个容器, 一个容器的迭代器。 当进行插入时, 就在迭代器指向位置进行插入。
inserter_iterator(Container& con, typename Container::iterator iter)
:_con(&con)
,_iter(iter)
{}

inserter_iterator<Container> operator=(typename Container::value_type x)
{
_con->insert(_iter, x);
++_iter;
return *this;
}

inserter_iterator<Container> operator*() { return *this; }
inserter_iterator<Container> operator++() { return *this; }
inserter_iterator<Container> operator++(int) { return *this; }

private:
Container* _con;
typename Container::iterator _iter;
};

template<class Container>
inserter_iterator<Container> inserter(Container& con, typename Container::iterator iter)
{
return inserter_iterator<Container>(con);
}

Reverse_iterator

        这个适配器应该是除了Container Adaptor,友友们最熟悉的一种适配器。 而且友友们如果熟悉list的底层封装, 其实就应该使用过Reverse_iterator封装过list的迭代器。 同样的,和其他适配器一样。 Reverse_iterator的封装屏蔽了list迭代器的底层细节, 并修改相应的接口。同时还使用了萃取获得需要使用的迭代器特性。

            那么为什么会有Reverse_iterator?

        我们可以看一下vs中那些双向序列容器的底层源码

其他双向数列容器这里不进行举例, 但是只要你找对应的源码就会发现, 所有双向序列容器的reverse_iterator都是通过reverse_iterator封装来的。 (关于反向迭代器的加加和减减操作, 这里并没有讲解, 个人认为这里最重要的是理解这个萃取的概念。

以下为底层源码实现:

	template<class Container>
inserter_iterator<Container> inserter(Container& con, typename Container::iterator iter)
{
return inserter_iterator<Container>(con);
}


template<class Iterator>
class reverse_Iterator
{
public:
//先进行萃取:
typedef typename Iterator::iterator_catecory iterator_catecory;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;



reverse_Iterator(Iterator it)
:_cur(it)
{}

//拷贝构造
reverse_Iterator(const reverse_Iterator<Iterator>& it)
:_cur(it._cur)
{}

reverse_Iterator& operator++()
{
--_cur;
return *this;
}

reverse_Iterator operator++(int)
{
Iterator tmp = _cur;
--_cur;
return tmp;
}

reverse_Iterator& operator--()
{
++_cur;
return *this;
}

reverse_Iterator operator--(int)
{
Iterator tmp = _cur;
++_cur;
return tmp;
}

//*
reference operator*()
{
Iterator tmp = _cur;
--tmp;
return *tmp;
}

pointer operator->()
{
Iterator tmp = _cur;
--tmp;
return
}

bool operator!=(const Iterator& it)
{
return _cur != it._cur;
}

bool operator==(const Iterator& it)
{
return _cur == it._cur;
}


Iterator _cur;
};

stream iterator

        stram_iterator可以将一个迭代器对象绑定到一个数据流身上。 如果是绑定到istream身上, 那么就是istream_iterator。 如果是绑定到ostream身上, 那么就是ostream_iterator。这个时候它们分别有输入(读)或者输出(写)的能力。

        这个的本质其实就是利用stream_iterator适配器将流对象封装起来。 然后对外修改一下接口——将原本的>> 或者 << 封装成赋值操作。并且每次加加都会赋值。 也就是连续插入或者提取。

istream_iterator

        需要注意的是, 我们在定义istream_iterator对象的时候就会进行一次读取数据。所以, 使用该适配器需要在特定的地方进行使用。

	template<class T, class Distance = ptrdiff_t>
class istream_iterator
{
template<class T>
friend bool operator!=(istream_iterator& x1, istream_iterator
public:

//先定义迭代器特性
typedef input_iterator_tag iterator_category;
typedef T value_type;
typedef T* pointer;
typedef T
typedef Distance difference_type;

//无参构造函数, 构造出来的istream对象是一个判断是否读取错误的EOF
istream_iterator()
:_stream(&cin)
, end_marker(false)
{}

//istream对象
istream_iterator(istream stream)
:_stream(&stream)
{
read();
}

//对该对象解引用就是获得上一个提取的数据
reference operator*() { return value; }
pointer operator->() { return &(operator*()); }

//迭代器前进的时候, 就要读取数据
istream_iterator<T, Distance> operator++()
{
read();
return *this;
}

istream_iterator<T, Distance> operator++(int)
{
auto tmp = *this;
read();
return tmp;
}

private:
istream* _stream;
T value;
bool end_marker; //用来标记是否到了读取错误。一般会定义一个错误对象来进行比较
void read()
{
if (end_marker) *_stream >> value;
if (*_stream) end_marker = true;
else end_marker = false;
}
};

ostream_iterator

        ostram_iterator不同于istream, 它的内部实现不需要保存数据。只需要将一个赋值接口将用户传来的数据打印即可。 


template<class T>
class ostream_iterator
{
public:
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void reference;
typedef void pointer;

ostream_iterator(ostream out)
:_stream(&out)
,_str(nullptr)
{}

ostream_iterator(ostream out, const char* str)
:_stream(&out)
, _str(str)
{}

//对迭代器进行赋值操作, 就是将要进行输出的数据进行打印
ostream_iterator<T> operator=(const T& x)
{
*_stream << x;
if (_str) *_stream << _str;
return *this;
}

//前置加加, 解引用, 后置加加都是自己本身
ostream_iterator<T> operator*() { return *this; }
ostream_iterator<T> operator++() { return *this; }
ostream_iterator<T> operator++(int) { return *this; }

private:
ostream* _stream;
const char* _str;
};

Function Adaptor

bind(系结)

bind1st

	template<class Operation>
class binder1st : public unary_function<typename Operation::second_argument_type,
typename Operation::result_type>
{
public:
//conductor, 将第一个参数进行绑定。
binder1st(const Operation& op, const typename Operation::first_argument_type& y)
:_op(op)
,_value(y)
{}

//这里是调用仿函数, 系结好的仿函数第一个参数已经被绑定。 只接收外部传来的第二个参数
typename Operation::result_type operator()(const typename Operation::second_argument_type& x)
{
return _op(_value, x); //实际上用的就是构造的时候的第一个参数,圆括号时候的第二个参数。
}
private:
Operation _op;
typename Operation::second_argument_type _value;
};

//辅助函数, 方便使用。 传要封装的仿函数, 以及第一个参数。
template<class Operation, class T>
inline binder1st<Operation> bind1st(Operation& op, const T& x)
{
return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}

bind2st

	template<class Operation, class T>
inline binder1st<Operation> bind1st(Operation& op, const T& x)
{
return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}

template<class Operation>
class binder2nd : public unary_function<typename Operation::first_argument_type,
typename Operation::result_type>
{
public:
//conductor, 将第二个参数进行绑定。
binder2nd(const Operation& op, const typename Operation::second_argument_type& x)
:_op(op)
,_value(x)
{}
typename Operation::result_type operator()(const typename Operation::first_argument_type& x)
{
return _op(x, _value);
}

private:
Operation _op;
typename Operation::first_argument_type _value;
};

template<class Operation, class T>
inline binder2nd<Operation> bind2nd(Operation& op, const T& x)
{
return binder2nd<Operation>(op, typename Operation::first_argument_type(x));
}

not(否定)

not用来表示一个仿函数Adaptable Predicate(可返回真假且可配接表达式)的逻辑负值。 以下是两个否定适配器的代码实现:

not1

	template<class Predicate>
class unary_negate : public unary_function<typename Predicate::argument_type,
typename Predicate::result_type>
{
public:
//利用会返回真假表达式进行适配。
unary_negate(const Predicate& x)
:_pred(x)
{}

//就是对原本的表达式进行一下取反操作
bool operator()(const typename Predicate::argument_type& x) const
{
return !_pred(x);
}

private:
Predicate _pred;
};

//辅助函数, 方便使用:
template<class Predicate>
inline unary_negate<Predicate> not1(const Predicate& pred)
{
return unary_negate<Predicate>(pred);
}

not2

	template<class Predicate>
inline unary_negate<Predicate> not1(const Predicate& pred)
{
return unary_negate<Predicate>(pred);
}

template<class Predicate>
class binary_negate : public binary_function<typename Predicate::first_argument_type,
typename Predicate::second_argument_type, typename Predicate::result_type>
{
public:
binary_negate(const Predicate& x)
:_pred(x)
{}
//
bool operator()(const typename Predicate::first_argument_type x, typename Predicate::second_argument_type y) const
{
return !_pred(x, y);
}

private:
Predicate _pred;
};

template<class Predicate>
inline binary_negate<Predicate> not2(const Predicate& pred)
{
return binary_negate<Predicate>(pred);
}

修饰

ptr_fun

ptr_fun是将函数修饰为仿函数的适配器。 ptr_fun需要定义两份, 一份用来适配一元函数,一份用来适配二元函数。

一元函数:

一元函数适配器其实就是把一个一元函数包起来, 然后()运算符就是调用这个一元函数

	template<class Arg, class Result>
class pointer_to_unary_function : public unary_function<Arg, Result>
{
typedef Result(*F_P) (Arg);
public:
pointer_to_unary_function() {}

//带参构造用来接收函数指针。
pointer_to_unary_function(F_P ptr)
:_ptr(ptr)
{}

//通过()调用该函数。
Result operator()(const Arg& x)
{
return _ptr(x);
}

private:
F_P _ptr; //一元函数成员
};

//辅助函数, 方便使用
template<class Arg, class Result>
inline pointer_to_unary_function<Arg, Result> ptr_fun(Result(*ptr)(Arg))
{
return pointer_to_unary_function<Arg, Result>(ptr);
}

二元函数

二元函数其实就是把二元函数包起来, 然后()就是调用这个二元函数

	template<class Arg1, class Arg2, class Result>
class pointer_to_binary_function : public binary_function<Arg1, Arg2, Result>
{
typedef Result(*F_P) (Arg1, Arg2);
public:
pointer_to_binary_function() {}

pointer_to_binary_function(F_P* ptr)
:_ptr(ptr)
{}

//
Result operator()(const Arg1 x1, const Arg2 x2)
{
return _ptr(x1, x2);
}
private:
F_P _ptr;
};

//辅助函数, 方便调用
template<class Arg1, class Arg2, class Result>
inline pointer_to_binary_function<Arg1, Arg2, Result> ptr_fun(Result(*ptr)(Arg1, Arg2))
{
return pointer_to_binary_function<Arg1, Arg2, Result>(ptr);
}
举报

相关推荐

0 条评论