C++ 如何实现 STL 风格的迭代器并避免常见的陷阱?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8054273/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 17:56:15  来源:igfitidea点击:

How to implement an STL-style iterator and avoid common pitfalls?

c++iteratorconst-iterator

提问by Tamás Szelei

I made a collection for which I want to provide an STL-style, random-access iterator. 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. What are the requirements for an iterator to be "STL-style" and what are some other pitfalls to avoid (if any)?

我制作了一个集合,我想为其提供一个 STL 风格的随机访问迭代器。我正在四处寻找迭代器的示例实现,但没有找到。我知道需要[]*运算符的常量重载。迭代器成为“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)。

采纳答案by Mooing Duck

http://www.cplusplus.com/reference/std/iterator/has a handy chart that details the specs of § 24.2.2 of the C++11 standard. Basically, the iterators have tags that describe the valid operations, and the tags have a hierarchy. Below is purely symbolic, these classes don't actually exist as such.

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.

You can either specialize std::iterator_traits<youriterator>, or put the same typedefs in the iterator itself, or inherit from std::iterator(which has these typedefs). I prefer the second option, to avoid changing things in the stdnamespace, and for readability, but most people inherit from std::iterator.

您可以专门化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
};

Note the iterator_category should be one of std::input_iterator_tag, std::output_iterator_tag, std::forward_iterator_tag, std::bidirectional_iterator_tag, or std::random_access_iterator_tag, depending on which requirements your iterator satisfies. Depending on your iterator, you may choose to specialize std::next, std::prev, std::advance, and std::distanceas well, but this is rarely needed. In extremely rarecases you may wish to specialize std::beginand std::end.

注意的iterator_category应该是一个std::input_iterator_tagstd::output_iterator_tagstd::forward_iterator_tagstd::bidirectional_iterator_tag,或者std::random_access_iterator_tag,这取决于你的需求满足的迭代器。根据您的迭代器,你可以选择专攻std::nextstd::prevstd::advance,和std::distance为好,但这个很少用到。在极少数情况下,您可能希望专攻std::beginstd::end

Your container should probably also have a const_iterator, which is a (possibly mutable) iterator to constant data that is similar to your iteratorexcept it should be implicitly constructable from a iteratorand users should be unable to modify the data. It is common for its internal pointer to be a pointer to non-constant data, and have iteratorinherit from const_iteratorso as to minimize code duplication.

您的容器可能还应该有 a const_iterator,它是一个(可能是可变的)常量数据的迭代器,它类似于您的常量数据,iterator但它应该可以从 a 隐式构造,iterator并且用户应该无法修改数据。它的内部指针通常是指向非常量数据的指针,并且iterator继承自const_iterator以减少代码重复。

My post at Writing your own STL Containerhas a more complete container/iterator prototype.

我在写你自己的 STL 容器的帖子有一个更完整的容器/迭代器原型。

回答by Michael Kristofik

The iterator_facade documentationfrom Boost.Iterator provides what looks like a nice tutorial on implementing iterators for a linked list. Could you use that as a starting point for building a random-access iterator over your container?

Boost.Iterator的iterator_facade 文档提供了一个关于为链表实现迭代器的不错的教程。您可以将其用作在容器上构建随机访问迭代器的起点吗?

If nothing else, you can take a look at the member functions and typedefs provided by iterator_facadeand use it as a starting point for building your own.

如果不出意外,您可以查看由 提供的成员函数和 typedef,iterator_facade并将其用作构建您自己的的起点。

回答by Gnawme

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?

回答by Valdemar_Rudolfovich

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

回答by Christian Rau

First of all you can look herefor 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_traitsfor it and provide some necessary typedefs (like iterator_categoryor value_type) or alternatively derive it from std::iterator, which defines the needed typedefs for you and can therefore be used with the default std::iterator_traits.

接下来,当您创建了迭代器类时,您需要对其进行专门化std::iterator_traits并提供一些必要的typedefs(如iterator_categoryvalue_type),或者从 派生它std::iterator,它typedef为您定义了所需的s,因此可以与默认的 s 一起使用std::iterator_traits

disclaimer:I know some people don't like cplusplus.comthat much, but they provide some really useful information on this.

免责声明:我知道有些人不喜欢cplusplus.com那么多,但他们提供了一些非常有用的信息。

回答by Richard Chambers

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. 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.

以下内容是在 MFC 测试应用程序上使用 Visual Studio 2017 社区版完成的。我将此作为示例,因为这篇帖子是我遇到的几个帖子之一,提供了一些帮助,但仍不足以满足我的需求。

The structcontaining the memory resident data looked something like the following. 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).

struct含有常驻内存的数据看起来像下面这样。为简洁起见,我删除了大部分元素,也没有包括使用的预处理器定义(使用的 SDK 既适用于 C 也适用于 C++,并且是旧的)。

What I was interested in doing is having iterators for the various WCHARtwo 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, CFileParacontains the templated proxy class (MnemonicIteratorDimSizeand 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_ttext string arrays. 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.

模板化代理类及其基类如下。我需要适应几种不同类型的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;
    }

};

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 CListCtrlobject with a list of mnemonics. I am using std::unique_ptrso that when the proxy class i not longer needed and the std::unique_ptrgoes out of scope, the memory will be cleaned up.

代理类及其迭代器的使用如下循环所示,以CListCtrl使用助记符列表填充对象。我正在使用std::unique_ptr这样当我不再需要代理类并且std::unique_ptr超出范围时,内存将被清理。

What this source code does is to create a proxy object for the array within the structwhich corresponds to the specified mnemonic identifier. It then creates an iterator for that object, uses a ranged forto fill in the CListCtrlcontrol and then cleans up. These are all raw wchar_ttext 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.

此源代码的作用是为struct对应于指定助记符标识符的数组创建一个代理对象。然后它为该对象创建一个迭代器,使用 rangedfor填充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++;
    }

回答by Samaursa

I was/am in the same boat as you for different reasons (partly educational, partly constraints). I had to re-write all the containers of the standard library and the containers had to conform to the standard. That means, if I swap out my container with the stlversion, the code would work the same. Which also meant that I had to re-write the iterators.

由于不同的原因(部分是教育,部分是限制),我和你在同一条船上。我不得不重新编写标准库的所有容器,并且容器必须符合标准。这意味着,如果我用stl版本换出我的容器,代码将工作相同。这也意味着我必须重新编写迭代器。

Anyway, I looked at EASTL. Apart from learning a ton about containers that I never learned all this time using the stlcontainers or through my undergraduate courses. The main reason is that EASTLis more readable than the stlcounterpart (I found this is simply because of the lack of all the macros and straight forward coding style). There are some icky things in there (like #ifdefs for exceptions) but nothing to overwhelm you.

无论如何,我看着EASTL。除了使用stl容器或通过我的本科课程学习了大量关于容器的知识之外,我从来没有学习过这些知识。主要原因是EASTL比对应的stl更具可读性(我发现这仅仅是因为缺少所有宏和直接的编码风格)。那里有一些令人讨厌的东西(例如#ifdefs 表示例外),但没有什么能让你不知所措。

As others mentioned, look at cplusplus.com's reference on iterators and containers.

正如其他人提到的,请查看 cplusplus.com 关于迭代器和容器的参考。

回答by Gabriel

And now a keys iterator for range-based for loop.

现在是基于范围的 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); }

Usage:

用法:

std::map<std::string,int> my_map;
// fill my_map
for (const std::string & k : keys(my_map))
{
    // do things
}

That's what i was looking for. But nobody had it, it seems.

这就是我要找的。但似乎没有人拥有它。

You get my OCD code alignment as a bonus.

你得到我的强迫症代码对齐作为奖励。

As an exercise, write your own for values(my_map)

作为练习,写下你自己的 values(my_map)