我做了一个集合,我想为它提供一个stl风格的随机访问迭代器。我正在寻找迭代器的示例实现,但我没有找到任何。我知道需要对[]和*操作符进行const重载。“stl风格”的迭代器的要求是什么?还有哪些需要避免的陷阱(如果有的话)?
附加上下文:这是一个库,除非真的需要,否则我不想对它引入任何依赖。我编写了自己的集合,以便能够使用相同的编译器在c++ 03和c++ 11之间提供二进制兼容性(因此没有可能会损坏的STL)。
我做了一个集合,我想为它提供一个stl风格的随机访问迭代器。我正在寻找迭代器的示例实现,但我没有找到任何。我知道需要对[]和*操作符进行const重载。“stl风格”的迭代器的要求是什么?还有哪些需要避免的陷阱(如果有的话)?
附加上下文:这是一个库,除非真的需要,否则我不想对它引入任何依赖。我编写了自己的集合,以便能够使用相同的编译器在c++ 03和c++ 11之间提供二进制兼容性(因此没有可能会损坏的STL)。
当前回答
托马斯·贝克尔在这里写了一篇关于这个主题的有用文章。
还有一种(也许更简单)的方法出现在前面的SO:如何正确地实现自定义迭代器和const_iterators?
其他回答
现在是一个基于范围的for循环的keys迭代器。
template<typename C>
class keys_it
{
typename C::const_iterator it_;
public:
using key_type = typename C::key_type;
using pointer = typename C::key_type*;
using difference_type = std::ptrdiff_t;
keys_it(const typename C::const_iterator & it) : it_(it) {}
keys_it operator++(int ) /* postfix */ { return it_++ ; }
keys_it& operator++( ) /* prefix */ { ++it_; return *this ; }
const key_type& operator* ( ) const { return it_->first ; }
const key_type& operator->( ) const { return it_->first ; }
keys_it operator+ (difference_type v ) const { return it_ + v ; }
bool operator==(const keys_it& rhs) const { return it_ == rhs.it_; }
bool operator!=(const keys_it& rhs) const { return it_ != rhs.it_; }
};
template<typename C>
class keys_impl
{
const C & c;
public:
keys_impl(const C & container) : c(container) {}
const keys_it<C> begin() const { return keys_it<C>(std::begin(c)); }
const keys_it<C> end () const { return keys_it<C>(std::end (c)); }
};
template<typename C>
keys_impl<C> keys(const C & container) { return keys_impl<C>(container); }
用法:
std::map<std::string,int> my_map;
// fill my_map
for (const std::string & k : keys(my_map))
{
// do things
}
这正是我要找的。但似乎没有人拥有它。
我给你的强迫症编码是额外奖励。
作为练习,编写您自己的值(my_map)
https://cplusplus.com/reference/iterator/有一个方便的图表,详细说明了c++ 11标准§24.2.2的规格。基本上,迭代器具有描述有效操作的标记,并且这些标记具有层次结构。下面是纯象征性的,这些类实际上并不存在。
iterator {
iterator(const iterator&);
~iterator();
iterator& operator=(const iterator&);
iterator& operator++(); //prefix increment
reference operator*() const;
friend void swap(iterator& lhs, iterator& rhs); //C++11 I think
};
input_iterator : public virtual iterator {
iterator operator++(int); //postfix increment
value_type operator*() const;
pointer operator->() const;
friend bool operator==(const iterator&, const iterator&);
friend bool operator!=(const iterator&, const iterator&);
};
//once an input iterator has been dereferenced, it is
//undefined to dereference one before that.
output_iterator : public virtual iterator {
reference operator*() const;
iterator operator++(int); //postfix increment
};
//dereferences may only be on the left side of an assignment
//once an output iterator has been dereferenced, it is
//undefined to dereference one before that.
forward_iterator : input_iterator, output_iterator {
forward_iterator();
};
//multiple passes allowed
bidirectional_iterator : forward_iterator {
iterator& operator--(); //prefix decrement
iterator operator--(int); //postfix decrement
};
random_access_iterator : bidirectional_iterator {
friend bool operator<(const iterator&, const iterator&);
friend bool operator>(const iterator&, const iterator&);
friend bool operator<=(const iterator&, const iterator&);
friend bool operator>=(const iterator&, const iterator&);
iterator& operator+=(size_type);
friend iterator operator+(const iterator&, size_type);
friend iterator operator+(size_type, const iterator&);
iterator& operator-=(size_type);
friend iterator operator-(const iterator&, size_type);
friend difference_type operator-(iterator, iterator);
reference operator[](size_type) const;
};
contiguous_iterator : random_access_iterator { //C++17
}; //elements are stored contiguously in memory.
你可以专门化std::iterator_traits<youriterator>,或者在迭代器本身中放入相同的类型defs,或者从std::iterator继承(它有这些类型defs)。我更喜欢第二种选择,为了避免在std命名空间中更改内容,也为了可读性,但大多数人都继承了std::iterator。
struct std::iterator_traits<youriterator> {
typedef ???? difference_type; //almost always ptrdiff_t
typedef ???? value_type; //almost always T
typedef ???? reference; //almost always T& or const T&
typedef ???? pointer; //almost always T* or const T*
typedef ???? iterator_category; //usually std::forward_iterator_tag or similar
};
注意,iterator_category应该是std::input_iterator_tag、std::output_iterator_tag、std::forward_iterator_tag、std::bidirectional_iterator_tag或std::random_access_iterator_tag之一,这取决于你的迭代器满足哪些要求。根据迭代器的不同,你也可以选择特化std::next、std::prev、std::advance和std::distance,但这很少需要。在极少数情况下,您可能希望专门化std::begin和std::end。
你的容器可能还应该有一个const_iterator,它是一个指向常量数据的迭代器(可能是可变的),与你的迭代器类似,只是它应该是隐式可从迭代器构造的,用户应该不能修改数据。它的内部指针通常是指向非常量数据的指针,并且iterator继承自const_iterator,以尽量减少代码重复。
我在写自己的STL容器的文章中有一个更完整的容器/迭代器原型。
托马斯·贝克尔在这里写了一篇关于这个主题的有用文章。
还有一种(也许更简单)的方法出现在前面的SO:如何正确地实现自定义迭代器和const_iterators?
Boost中的iterator_facade文档。Iterator提供了一个关于为链表实现迭代器的很好的教程。您是否可以将其作为在容器上构建随机访问迭代器的起点?
如果没有其他方法,可以查看iterator_facade提供的成员函数和类型定义,并将其作为构建自己的成员函数和类型定义的起点。
由于不同的原因(部分是教育,部分是限制),我和你在同一条船上。我必须重写标准库的所有容器,并且容器必须符合标准。这意味着,如果我用stl版本替换我的容器,代码将工作相同。这也意味着我必须重写迭代器。
总之,我看了EASTL。除了在使用stl容器或在本科课程中学习了大量关于容器的知识之外。主要原因是EASTL比stl更具可读性(我发现这只是因为缺少所有的宏和直接的编码风格)。里面有一些讨厌的东西(比如异常的#ifdefs),但没有什么能让你不知所措。
正如其他人提到的,查看cplusplus.com关于迭代器和容器的参考。