为什么
在很多情况下,集合内容被封装到特定类里,只能通过一些接口来进行访问,而如果是STL容器,则可以利用STL算法进行非常灵活、表达能力更强的操作,譬如如下类:
class FakeVector
{
public:
std::size_t count() const ;
int item(std::size_t idx) ;
void add(int v);
};
在对FakeVector
进行操作时,只能有如下写法:
FakeVector v(items);
//查询
for (std::size_t i = 0; i < v.count(); i++)
{
auto item = v.item(i);
}
//追加
v.add(1024);
而不能像操作STL容器一样使用std::copy
和std::transform
来进行查询、追加操作。
目标
为现有类提供迭代器,使其能够模仿STL容器来应用STL相关算法,譬如能够采用如下方式来操作FakeVector
std::vector<int> results;
FakeVector v(items);
STLAdapter<int, FakeVector> proxy(&v);
std::copy(proxy.begin(), proxy.end(), std::back_inserter(results));
std::transform(proxy.begin(), proxy.end(), std::back_inserter(results),
[](int v)->int { return v*2; });
std::copy(results.rbegin(), results.rend(), std::back_inserter(proxy));
实现思路
STL容器和算法之间的桥梁是迭代器,可以实现定制的迭代器来实现在指定类上应用算法;鉴于要支持的遗留代码集合类接口命名不一致,要为迭代器提供辅助类来获取迭代器所需信息;遗留代码无法修改,所以需要一个代理类来为STL算法提供迭代器。
迭代器辅助类
如果想要能够查询和增加集合里的内容,只需要以下接口:
- 获取个数
count
- 根据索引获取内容
item
- 追加内容
append
而且根据集合类无法正确推导出集合内容类型,定义如下辅助模板类,用来为迭代器提供统一的操作接口:
template<typename T,typename Container>
struct STLAdapterHelper
{
typedef typename T value_type;
static std::size_t count(Container* container);
static value_type item(Container* container,std::size_t idx);
static void append(Container* container, value_type const& v);
};
迭代器初步
书写迭代器最省事便捷的方法就是继承自std::iterator
、继承时指定迭代器类型及值类型;如果仅需支持单向查询操作则指定迭代器类型为std::input_iterator_tag
即可。
std::input_iterator_tag
这种迭代器类型对应于InputIterator
,需要实现以下接口:
typename iterator::value_type operator*() const;
iterator& operator++();
iterator operator++(int);
friend bool operator==(const iterator& lhs, const iterator& rhs);
friend bool operator!=(const iterator& lhs, const iterator& rhs);
其中operator*()
完成迭代器指定内容访问动作,返回容器里对应未知的内容;operator++()
是前置自增;operator++(int)
是后置自增;另外两个接口是比较操作。
基于索引的迭代器实现
基于索引的迭代器实现比较简单,迭代器包含了当前索引以及容器;STL算法在操作迭代器时,行为与手写for循环条件类似,首先拿到begin和end,确定起始位置和终止条件, 获取当前迭代器位置的值,然后自增移动到下一个迭代器位置。
如下是一个常规的查询迭代器实现:
class iterator :public std::iterator<std::input_iterator_tag, typename STLAdapter::value_type>
{
public:
iterator(STLAdapter& proxy, std::size_t idx)
:m_idx(idx), m_proxy(proxy) {};
//access
typename iterator::value_type operator*() const {
//根据当前索引及集合获取对应值/内容
}
//std::input_iterator_tag
iterator& operator++() {
m_idx += 1;
return *this;
}
iterator operator++(int) {
iterator tmp = *this;
++*this;
return tmp;
}
friend bool operator==(const iterator& lhs, const iterator& rhs) {
return (&lhs.m_proxy == &rhs.m_proxy) && (lhs.m_idx == rhs.m_idx);
}
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
return !(lhs == rhs);
}
private:
std::size_t m_idx;
STLAdapter& m_proxy;
};
集合类代理
代理类包含了要操作的集合,并向外提供迭代器来支持STL算法,实现如下:
template<typename T,typename Container>
class STLAdapter
{
public:
typedef STLAdapterHelper<T, Container> helper;
typedef typename helper::value_type value_type;
explicit STLAdapter(Container* container)
:m_object(container)
{
if (m_object == nullptr)
throw std::invalid_argument("STLAdapter(nullptr) donot support!");
};
class iterator{
//迭代器实现
};
typedef const iterator const_iterator;
typedef const typename iterator::reference const_reference;
//提供迭代器起始与解释位置
iterator begin() const { return iterator(const_cast<STLAdapter&>(*this), 0); };
iterator end() const { return iterator(const_cast<STLAdapter&>(*this), helper::count(m_object));};
//提供const版
const_iterator cbegin() const { return begin(); };
const_iterator cend() const { return end(); };
protected:
Container* m_object;
};
之后就可以为指定集合类提供STLAdapterHelper
模板偏特化来支持STL的非修改算法了。
如果采用std::back_inserter
希望修改集合类的内容,需要提供push_back
方法(VS2010),譬如在STLAdapter
中提供:
void push_back(typename value_type const& v) {
STLAdapterHelper<T, Container>::append(m_object, v);
}
适配指定的集合类
现在就可以偏特化STLAdapterHelper
来提供指定集合类的适配了,示例如下:
//模板偏特化
template<>
struct STLAdapterHelper<int,FakeVector>
{
typedef int value_type;
static std::size_t count(FakeVector* container) {
return container->count();
}
static int item(FakeVector* container, std::size_t idx) {
return container->item(idx);
}
static void append(FakeVector* container, int const& v) {
container->add(v);
};
};
未完成的内容
经过上述操作,已经可以对特定的集合类提供STLAdapter
来应用STL算法了;但是现在除了查询和追加操作,是无法对集合类进行其它操作的,如果想进行其它操作,可以提供如下操作:
Container* operator->() { return m_object; };
通过运算符重载,使用STAdapter->
即可获取集合类进行之前无法处理的操作。
完整的实现
#include <iterator>
template<typename T,typename Container>
struct STLAdapterHelper
{
typedef typename T value_type;
static std::size_t count(Container* container);
static value_type item(Container* container,std::size_t idx);
static void append(Container* container, value_type const& v);
};
template<typename T,typename Container>
class STLAdapter
{
public:
typedef STLAdapterHelper<T, Container> helper;
typedef typename helper::value_type value_type;
explicit STLAdapter(Container* container)
:m_object(container)
{
if (m_object == nullptr)
throw std::invalid_argument("STLAdapter(nullptr) donot support!");
};
class iterator :public std::iterator<std::input_iterator_tag, typename STLAdapter::value_type>
{
public:
iterator(STLAdapter& proxy, std::size_t idx)
:m_idx(idx), m_proxy(proxy) {};
//access
typename iterator::value_type operator*() const {
return helper::item(m_proxy.m_object,m_idx);
}
//std::input_iterator_tag
iterator& operator++() {
m_idx += 1;
return *this;
}
iterator operator++(int) {
iterator tmp = *this;
++*this;
return tmp;
}
friend bool operator==(const iterator& lhs, const iterator& rhs) {
return (&lhs.m_proxy == &rhs.m_proxy) && (lhs.m_idx == rhs.m_idx);
}
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
return !(lhs == rhs);
}
private:
std::size_t m_idx;
STLAdapter& m_proxy;
};
typedef const iterator const_iterator;
typedef const typename iterator::reference const_reference;
iterator begin() const { return iterator(const_cast<STLAdapter&>(*this), 0); };
iterator end() const { return iterator(const_cast<STLAdapter&>(*this), helper::count(m_object));};
const_iterator cbegin() const { return begin(); };
const_iterator cend() const { return end(); };
Container* operator->() { return m_object; };
void push_back(typename value_type const& v) {
helper::append(m_object, v);
}
protected:
Container* m_object;
};
学到的内容
- 迭代器原理及实现方法
- 如何对接遗留代码