我制作了一个集合,希望为其提供STL样式的随机访问迭代器。我在寻找迭代器的示例实现,但没有找到任何实现。我知道需要对const重载[]
和*
运算符。将迭代器设为“ STL样式”的要求是什么?还要避免其他陷阱(如果有)?
其他上下文:这是针对库的,除非真正需要,否则我不想引入任何依赖关系。我编写了自己的集合,以便能够使用相同的编译器在C ++ 03和C ++ 11之间提供二进制兼容性(因此不会破坏STL)。
我制作了一个集合,希望为其提供STL样式的随机访问迭代器。我在寻找迭代器的示例实现,但没有找到任何实现。我知道需要对const重载[]
和*
运算符。将迭代器设为“ STL样式”的要求是什么?还要避免其他陷阱(如果有)?
其他上下文:这是针对库的,除非真正需要,否则我不想引入任何依赖关系。我编写了自己的集合,以便能够使用相同的编译器在C ++ 03和C ++ 11之间提供二进制兼容性(因此不会破坏STL)。
Answers:
http://www.cplusplus.com/reference/std/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>
,或将相同的typedef放入迭代器本身,或从其继承std::iterator
(具有这些typedef)。我更喜欢第二种选择,以避免更改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
不同之处在于它应该可以从隐式构造,iterator
并且用户应该无法修改数据。它的内部指针通常是指向非恒定数据的指针,并且具有iterator
从其继承的指针,const_iterator
以最大程度地减少代码重复。
我在“ 编写自己的STL容器”一文中提供了更完整的容器/迭代器原型。
std::iterator_traits
自己专门定义typedef之外,您还可以仅从派生自std::iterator
,它根据模板参数为您定义它们。
const_iterator
。我的帖子还缺少什么?您似乎暗示着要在类中添加更多内容,但是问题特别是关于实现迭代器。
std::iterator
被提出在C ++ 17被弃用 ; 不是,但我不会希望它存在更长时间。
std::iterator
毕竟已被弃用。
operator bool
是非常危险的。有人会尝试使用它来检测范围的结束while(it++)
,但是它真正检查的只是迭代器是否使用参数构造。
Boost.Iterator 的iterator_facade文档提供了一个不错的教程,介绍如何为链表实现迭代器。您可以以此为基础在容器上构建随机访问迭代器吗?
如果没有其他问题,您可以看一下提供的成员函数和typedef,iterator_facade
并将其用作构建自己的成员的起点。
托马斯·贝克尔(Thomas Becker)在这里写了一篇有关该主题的有用文章。
SO上也出现过这种(也许更简单)的方法:如何正确实现自定义迭代器和const_iterators?
这是原始指针迭代器的示例。
您不应该使用迭代器类来处理原始指针!
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <assert.h>
template<typename T>
class ptr_iterator
: public std::iterator<std::forward_iterator_tag, T>
{
typedef ptr_iterator<T> iterator;
pointer pos_;
public:
ptr_iterator() : pos_(nullptr) {}
ptr_iterator(T* v) : pos_(v) {}
~ptr_iterator() {}
iterator operator++(int) /* postfix */ { return pos_++; }
iterator& operator++() /* prefix */ { ++pos_; return *this; }
reference operator* () const { return *pos_; }
pointer operator->() const { return pos_; }
iterator operator+ (difference_type v) const { return pos_ + v; }
bool operator==(const iterator& rhs) const { return pos_ == rhs.pos_; }
bool operator!=(const iterator& rhs) const { return pos_ != rhs.pos_; }
};
template<typename T>
ptr_iterator<T> begin(T *val) { return ptr_iterator<T>(val); }
template<typename T, typename Tsize>
ptr_iterator<T> end(T *val, Tsize size) { return ptr_iterator<T>(val) + size; }
基于原始指针范围的循环解决方法。请纠正我,如果有更好的方法从原始指针进行基于范围的循环。
template<typename T>
class ptr_range
{
T* begin_;
T* end_;
public:
ptr_range(T* ptr, size_t length) : begin_(ptr), end_(ptr + length) { assert(begin_ <= end_); }
T* begin() const { return begin_; }
T* end() const { return end_; }
};
template<typename T>
ptr_range<T> range(T* ptr, size_t length) { return ptr_range<T>(ptr, length); }
和简单的测试
void DoIteratorTest()
{
const static size_t size = 10;
uint8_t *data = new uint8_t[size];
{
// Only for iterator test
uint8_t n = '0';
auto first = begin(data);
auto last = end(data, size);
for (auto it = first; it != last; ++it)
{
*it = n++;
}
// It's prefer to use the following way:
for (const auto& n : range(data, size))
{
std::cout << " char: " << static_cast<char>(n) << std::endl;
}
}
{
// Only for iterator test
ptr_iterator<uint8_t> first(data);
ptr_iterator<uint8_t> last(first + size);
std::vector<uint8_t> v1(first, last);
// It's prefer to use the following way:
std::vector<uint8_t> v2(data, data + size);
}
{
std::list<std::vector<uint8_t>> queue_;
queue_.emplace_back(begin(data), end(data, size));
queue_.emplace_back(data, data + size);
}
}
首先,您可以在此处查找各个迭代器类型需要支持的各种操作的列表。
接下来,当您创建了迭代器类时,您需要对其专门化std::iterator_traits
并提供一些必要typedef
的(如iterator_category
或value_type
),或者从中派生它std::iterator
,它typedef
为您定义了所需的s,因此可以与default一起使用std::iterator_traits
。
免责声明:我知道有些人不太喜欢cplusplus.com
,但是他们提供了一些非常有用的信息。
由于不同的原因(部分是出于教育性,部分是出于限制),我和你在同一条船上。我必须重写标准库的所有容器,并且容器必须符合标准。这意味着,如果我用stl版本换出我的容器,则代码将工作相同。这也意味着我不得不重新编写迭代器。
无论如何,我看着EASTL。除了学习大量有关容器的知识之外,我从来没有使用过stl容器或我的本科课程来学习这些东西。主要原因是EASTL比stl更具可读性(我发现这仅仅是因为缺少所有宏和直接的编码风格)。里面有一些棘手的东西(例如#ifdefs作为例外),但是没有什么让您不知所措。
正如其他人提到的,请参阅cplusplus.com关于迭代器和容器的参考。
我正在尝试解决能够迭代几个不同文本数组的问题,所有这些文本数组都存储在一个很大的内存驻留数据库中struct
。
在MFC测试应用程序上使用Visual Studio 2017 Community Edition解决了以下问题。我将其作为示例,因为该帖子是我遇到的提供了一些帮助但仍不足以满足我需求的几个帖子之一。
该struct
含有常驻内存的数据看起来像下面这样。为了简洁起见,我删除了大多数元素,并且也未包括所使用的预处理器定义(使用的SDK适用于C以及C ++,并且已经很老了)。
我感兴趣的是为各种WCHAR
二维数组提供迭代器,其中包含助记符的文本字符串。
typedef struct tagUNINTRAM {
// stuff deleted ...
WCHAR ParaTransMnemo[MAX_TRANSM_NO][PARA_TRANSMNEMO_LEN]; /* prog #20 */
WCHAR ParaLeadThru[MAX_LEAD_NO][PARA_LEADTHRU_LEN]; /* prog #21 */
WCHAR ParaReportName[MAX_REPO_NO][PARA_REPORTNAME_LEN]; /* prog #22 */
WCHAR ParaSpeMnemo[MAX_SPEM_NO][PARA_SPEMNEMO_LEN]; /* prog #23 */
WCHAR ParaPCIF[MAX_PCIF_SIZE]; /* prog #39 */
WCHAR ParaAdjMnemo[MAX_ADJM_NO][PARA_ADJMNEMO_LEN]; /* prog #46 */
WCHAR ParaPrtModi[MAX_PRTMODI_NO][PARA_PRTMODI_LEN]; /* prog #47 */
WCHAR ParaMajorDEPT[MAX_MDEPT_NO][PARA_MAJORDEPT_LEN]; /* prog #48 */
// ... stuff deleted
} UNINIRAM;
当前的方法是使用模板为每个数组定义一个代理类,然后使用单个迭代器类,通过使用代表该数组的代理对象,该迭代器类可用于在特定数组上进行迭代。
内存驻留数据的副本存储在一个对象中,该对象处理从/向磁盘读取内存驻留数据。此类CFilePara
包含模板化的代理类(MnemonicIteratorDimSize
以及从其派生的子类MnemonicIteratorDimSizeBase
)和迭代器类MnemonicIterator
。
创建的代理对象附加到迭代器对象,该迭代器对象通过基类描述的接口访问必要的信息,所有代理类都从该基类派生而来。结果是只有一种迭代器类可以与几种不同的代理类一起使用,因为不同的代理类都公开相同的接口,即代理基类的接口。
第一件事是创建一组标识符,该标识符将提供给类工厂以生成该助记符类型的特定代理对象。这些标识符用作用户界面的一部分,以标识用户感兴趣的特定数据,并希望对其进行修改。
const static DWORD_PTR dwId_TransactionMnemonic = 1;
const static DWORD_PTR dwId_ReportMnemonic = 2;
const static DWORD_PTR dwId_SpecialMnemonic = 3;
const static DWORD_PTR dwId_LeadThroughMnemonic = 4;
代理类
模板化代理类及其基类如下。我需要容纳几种不同类型的wchar_t
文本字符串数组。二维数组具有不同数量的助记符,具体取决于助记符的类型(目的),并且不同类型的助记符具有不同的最大长度,介于五个文本字符和二十个文本字符之间。派生代理类的模板很自然,模板要求每个助记符中的字符数最大。创建代理对象后,我们便使用该SetRange()
方法指定实际的助记符数组及其范围。
// proxy object which represents a particular subsection of the
// memory resident database each of which is an array of wchar_t
// text arrays though the number of array elements may vary.
class MnemonicIteratorDimSizeBase
{
DWORD_PTR m_Type;
public:
MnemonicIteratorDimSizeBase(DWORD_PTR x) { }
virtual ~MnemonicIteratorDimSizeBase() { }
virtual wchar_t *begin() = 0;
virtual wchar_t *end() = 0;
virtual wchar_t *get(int i) = 0;
virtual int ItemSize() = 0;
virtual int ItemCount() = 0;
virtual DWORD_PTR ItemType() { return m_Type; }
};
template <size_t sDimSize>
class MnemonicIteratorDimSize : public MnemonicIteratorDimSizeBase
{
wchar_t (*m_begin)[sDimSize];
wchar_t (*m_end)[sDimSize];
public:
MnemonicIteratorDimSize(DWORD_PTR x) : MnemonicIteratorDimSizeBase(x), m_begin(0), m_end(0) { }
virtual ~MnemonicIteratorDimSize() { }
virtual wchar_t *begin() { return m_begin[0]; }
virtual wchar_t *end() { return m_end[0]; }
virtual wchar_t *get(int i) { return m_begin[i]; }
virtual int ItemSize() { return sDimSize; }
virtual int ItemCount() { return m_end - m_begin; }
void SetRange(wchar_t (*begin)[sDimSize], wchar_t (*end)[sDimSize]) {
m_begin = begin; m_end = end;
}
};
迭代器类
迭代器类本身如下。此类仅提供基本的正向迭代器功能,这是当前所需的全部功能。但是,我希望当我需要其他一些东西时,它会改变或扩展。
class MnemonicIterator
{
private:
MnemonicIteratorDimSizeBase *m_p; // we do not own this pointer. we just use it to access current item.
int m_index; // zero based index of item.
wchar_t *m_item; // value to be returned.
public:
MnemonicIterator(MnemonicIteratorDimSizeBase *p) : m_p(p) { }
~MnemonicIterator() { }
// a ranged for needs begin() and end() to determine the range.
// the range is up to but not including what end() returns.
MnemonicIterator & begin() { m_item = m_p->get(m_index = 0); return *this; } // begining of range of values for ranged for. first item
MnemonicIterator & end() { m_item = m_p->get(m_index = m_p->ItemCount()); return *this; } // end of range of values for ranged for. item after last item.
MnemonicIterator & operator ++ () { m_item = m_p->get(++m_index); return *this; } // prefix increment, ++p
MnemonicIterator & operator ++ (int i) { m_item = m_p->get(m_index++); return *this; } // postfix increment, p++
bool operator != (MnemonicIterator &p) { return **this != *p; } // minimum logical operator is not equal to
wchar_t * operator *() const { return m_item; } // dereference iterator to get what is pointed to
};
代理对象工厂根据助记符标识符确定要创建的对象。创建代理对象,并且返回的指针是标准基类类型,以便具有统一的接口,无论访问哪个不同的助记符节。该SetRange()
方法用于向代理对象指定代理代表的特定数组元素以及数组元素的范围。
CFilePara::MnemonicIteratorDimSizeBase * CFilePara::MakeIterator(DWORD_PTR x)
{
CFilePara::MnemonicIteratorDimSizeBase *mi = nullptr;
switch (x) {
case dwId_TransactionMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_TRANSMNEMO_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_TRANSMNEMO_LEN>(x);
mk->SetRange(&m_Para.ParaTransMnemo[0], &m_Para.ParaTransMnemo[MAX_TRANSM_NO]);
mi = mk;
}
break;
case dwId_ReportMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_REPORTNAME_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_REPORTNAME_LEN>(x);
mk->SetRange(&m_Para.ParaReportName[0], &m_Para.ParaReportName[MAX_REPO_NO]);
mi = mk;
}
break;
case dwId_SpecialMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_SPEMNEMO_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_SPEMNEMO_LEN>(x);
mk->SetRange(&m_Para.ParaSpeMnemo[0], &m_Para.ParaSpeMnemo[MAX_SPEM_NO]);
mi = mk;
}
break;
case dwId_LeadThroughMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_LEADTHRU_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_LEADTHRU_LEN>(x);
mk->SetRange(&m_Para.ParaLeadThru[0], &m_Para.ParaLeadThru[MAX_LEAD_NO]);
mi = mk;
}
break;
}
return mi;
}
使用代理类和迭代器
代理类及其迭代器的用法如以下循环所示,以CListCtrl
使用助记符列表填充对象。我正在使用,std::unique_ptr
以便当不再需要代理类并且std::unique_ptr
超出范围时,将清理内存。
此源代码执行的操作是为其中的数组创建一个代理对象,该对象struct
对应于指定的助记符标识符。然后,它为该对象创建一个迭代器,使用范围for
填充CListCtrl
控件,然后进行清理。这些都是原始wchar_t
文本字符串,它们可能恰好是数组元素的数目,因此我们将字符串复制到临时缓冲区中,以确保文本以零结尾。
std::unique_ptr<CFilePara::MnemonicIteratorDimSizeBase> pObj(pFile->MakeIterator(m_IteratorType));
CFilePara::MnemonicIterator pIter(pObj.get()); // provide the raw pointer to the iterator who doesn't own it.
int i = 0; // CListCtrl index for zero based position to insert mnemonic.
for (auto x : pIter)
{
WCHAR szText[32] = { 0 }; // Temporary buffer.
wcsncpy_s(szText, 32, x, pObj->ItemSize());
m_mnemonicList.InsertItem(i, szText); i++;
}
现在是用于基于范围的for循环的键迭代器。
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
}
那就是我想要的。但是似乎没有人拥有它。
您可以获得我的OCD代码对齐作为奖励。
作为练习,写自己的 values(my_map)