如何实现STL样式的迭代器并避免常见的陷阱?

本文翻译自:How to implement an STL-style iterator and avoid common pitfalls?

I made a collection for which I want to provide an STL-style, random-access iterator. 我制作了一个集合,希望为其提供STL样式的随机访问迭代器。 I was searching around for an example implementation of an iterator but I didn't find any. 我在寻找迭代器的示例实现,但没有找到任何实现。 I know about the need for const overloads of [] and * operators. 我知道需要[]*运算符的const重载。 What are the requirements for an iterator to be "STL-style" and what are some other pitfalls to avoid (if any)? 将迭代器设为“ STL样式”的要求是什么?还要避免其他陷阱(如果有)?

Additional context: This is for a library and I don't want to introduce any dependency on it unless I really need to. 其他上下文:这是针对库的,除非真正需要,否则我不想引入对它的任何依赖关系。 I write my own collection to be able to provide binary compatibility between C++03 and C++11 with the same compiler (so no STL which would probably break). 我编写了自己的集合,以便能够使用相同的编译器在C ++ 03和C ++ 11之间提供二进制兼容性(因此不会破坏STL)。


#1楼

参考:https://stackoom.com/question/xNhD/如何实现STL样式的迭代器并避免常见的陷阱


#2楼

Here is sample of raw pointer iterator. 这是原始指针迭代器的示例。

You shouldn't use iterator class to work with raw pointers! 您不应该使用迭代器类来处理原始指针!

#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; }

Raw pointer range based loop workaround. 基于原始指针范围的循环解决方法。 Please, correct me, if there is better way to make range based loop from raw pointer. 请纠正我,如果有更好的方法从原始指针进行基于范围的循环。

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); }

And simple test 和简单的测试

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);
    }
}

#3楼

I was trying to solve the problem of being able to iterate over several different text arrays all of which are stored within a memory resident database that is a large struct . 我试图解决能够迭代几个不同文本数组的问题,所有这些文本数组都存储在一个大型struct驻留内存的数据库中。

The following was worked out using Visual Studio 2017 Community Edition on an MFC test application. 在MFC测试应用程序上使用Visual Studio 2017 Community Edition解决了以下问题。 I am including this as an example as this posting was one of several that I ran across that provided some help yet were still insufficient for my needs. 我将其作为示例,因为此发布是我遇到的提供了一些帮助但仍不足以满足我需求的几种发布之一。

The struct containing the memory resident data looked something like the following. 包含内存驻留数据的struct如下所示。 I have removed most of the elements for the sake of brevity and have also not included the Preprocessor defines used (the SDK in use is for C as well as C++ and is old). 为了简洁起见,我已删除了大多数元素,并且也未包括所使用的预处理器定义(使用的SDK适用于C和C ++,并且已经很旧了)。

What I was interested in doing is having iterators for the various WCHAR two dimensional arrays which contained text strings for mnemonics. 我感兴趣的是为各种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;

The current approach is to use a template to define a proxy class for each of the arrays and then to have a single iterator class that can be used to iterate over a particular array by using a proxy object representing the array. 当前方法是使用模板为每个数组定义一个代理类,然后使用单个迭代器类,通过使用代表该数组的代理对象,该迭代器类可用于在特定数组上进行迭代。

A copy of the memory resident data is stored in an object that handles reading and writing the memory resident data from/to disk. 内存常驻数据的副本存储在一个对象中,该对象处理从磁盘到磁盘的内存常驻数据读写。 This class, CFilePara contains the templated proxy class ( MnemonicIteratorDimSize and the sub class from which is it is derived, MnemonicIteratorDimSizeBase ) and the iterator class, MnemonicIterator . 此类CFilePara包含模板化的代理类( MnemonicIteratorDimSize及其派生子类MnemonicIteratorDimSizeBase )和迭代器类MnemonicIterator

The created proxy object is attached to an iterator object which accesses the necessary information through an interface described by a base class from which all of the proxy classes are derived. 创建的代理对象附加到迭代器对象,该迭代器对象通过基类描述的接口访问必要的信息,所有代理类都从该基类派生而来。 The result is to have a single type of iterator class which can be used with several different proxy classes because the different proxy classes all expose the same interface, the interface of the proxy base class. 结果是只有一种迭代器类可以与几种不同的代理类一起使用,因为不同的代理类都公开相同的接口,即代理基类的接口。

The first thing was to create a set of identifiers which would be provided to a class factory to generate the specific proxy object for that type of mnemonic. 第一件事是创建一组标识符,该标识符将提供给类工厂以生成该助记符类型的特定代理对象。 These identifiers are used as part of the user interface to identify the particular provisioning data the user is interested in seeing and possibly modifying. 这些标识符用作用户界面的一部分,以标识用户感兴趣的特定数据,并希望对其进行修改。

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;

The Proxy Class 代理类

The templated proxy class and its base class are as follows. 模板化代理类及其基类如下。 I needed to accommodate several different kinds of wchar_t text string arrays. 我需要容纳几种不同类型的wchar_t文本字符串数组。 The two dimensional arrays had different numbers of mnemonics, depending on the type (purpose) of the mnemonic and the different types of mnemonics were of different maximum lengths, varying between five text characters and twenty text characters. 二维数组具有不同数量的助记符,具体取决于助记符的类型(用途),并且不同类型的助记符具有不同的最大长度,介于五个文本字符和二十个文本字符之间。 Templates for the derived proxy class was a natural fit with the template requiring the maximum number of characters in each mnemonic. 派生代理类的模板很自然,模板需要每个助记符中的最大字符数。 After the proxy object is created, we then use the SetRange() method to specify the actual mnemonic array and its range. 创建代理对象后,我们然后使用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;
    }

};

The Iterator Class 迭代器类

The iterator class itself is as follows. 迭代器类本身如下。 This class provides just basic forward iterator functionality which is all that is needed at this time. 此类仅提供基本的正向迭代器功能,这是当前所需的全部功能。 However I expect that this will change or be extended when I need something additional from it. 但是,我希望当我需要其他一些东西时,它会改变或扩展。

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
};

The proxy object factory determines which object to created based on the mnemonic identifier. 代理对象工厂根据助记符标识符确定要创建的对象。 The proxy object is created and the pointer returned is the standard base class type so as to have a uniform interface regardless of which of the different mnemonic sections are being accessed. 创建代理对象,并且返回的指针是标准基类类型,以便具有统一的接口,无论访问哪个不同的助记符节。 The SetRange() method is used to specify to the proxy object the specific array elements the proxy represents and the range of the array elements. 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;
}

Using the Proxy Class and Iterator 使用代理类和迭代器

The proxy class and its iterator are used as shown in the following loop to fill in a CListCtrl object with a list of mnemonics. 代理类及其迭代器,如以下循环所示,用于使用助记符列表填充CListCtrl对象。 I am using std::unique_ptr so that when the proxy class i not longer needed and the std::unique_ptr goes out of scope, the memory will be cleaned up. 我正在使用std::unique_ptr以便当不再需要代理类并且std::unique_ptr超出范围时,将清理内存。

What this source code does is to create a proxy object for the array within the struct which corresponds to the specified mnemonic identifier. 此源代码执行的操作是为struct的数组创建一个代理对象,该对象对应于指定的助记符标识符。 It then creates an iterator for that object, uses a ranged for to fill in the CListCtrl control and then cleans up. 然后,它为该对象创建一个迭代器,使用范围内for来填充CListCtrl控件,然后进行清理。 These are all raw wchar_t text strings which may be exactly the number of array elements so we copy the string into a temporary buffer in order to ensure that the text is zero terminated. 这些都是原始的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++;
    }

#4楼

The iterator_facade documentation from Boost.Iterator provides what looks like a nice tutorial on implementing iterators for a linked list. Boost.Iterator的iterator_facade文档提供了一个不错的教程,介绍如何为链表实现迭代器。 Could you use that as a starting point for building a random-access iterator over your container? 您可以以此为基础在容器上构建随机访问迭代器吗?

If nothing else, you can take a look at the member functions and typedefs provided by iterator_facade and use it as a starting point for building your own. 如果没有其他问题,您可以查看iterator_facade提供的成员函数和typedef,并将其用作构建自己的成员的起点。


#5楼

Thomas Becker wrote a useful article on the subject here . 托马斯·贝克尔(Thomas Becker)在这里写了一篇有关该主题的有用文章。

There was also this (perhaps simpler) approach that appeared previously on SO: How to correctly implement custom iterators and const_iterators? SO上也出现过这种(也许更简单)的方法: 如何正确实现自定义迭代器和const_iterators?


#6楼

First of all you can look here for a list of the various operations the individual iterator types need to support. 首先,您可以在此处查找各个迭代器类型需要支持的各种操作的列表。

Next, when you have made your iterator class you need to either specialize std::iterator_traits for it and provide some necessary typedef s (like iterator_category or value_type ) or alternatively derive it from std::iterator , which defines the needed typedef s for you and can therefore be used with the default std::iterator_traits . 接下来,当您创建了迭代器类后,您需要为其专门化std::iterator_traits并提供一些必要的typedef (例如iterator_categoryvalue_type ),或者从std::iterator派生它,后者为您定义了所需的typedef 。因此可以与默认的std::iterator_traits

disclaimer: I know some people don't like cplusplus.com that much, but they provide some really useful information on this. 免责声明:我知道有些人不太喜欢cplusplus.com ,但是他们提供了一些非常有用的信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值