Wie implementiere ich einen Iterator im STL-Stil und vermeide häufige Fallstricke?

306

Ich habe eine Sammlung erstellt, für die ich einen STL-artigen Iterator mit wahlfreiem Zugriff bereitstellen möchte. Ich habe nach einer Beispielimplementierung eines Iterators gesucht, aber keine gefunden. Ich weiß um die Notwendigkeit von Konstantenüberladungen []und *Operatoren. Was sind die Anforderungen an einen Iterator im "STL-Stil" und welche anderen Fallstricke sind zu vermeiden (falls vorhanden)?

Zusätzlicher Kontext: Dies ist für eine Bibliothek und ich möchte keine Abhängigkeit davon einführen, es sei denn, ich muss es wirklich. Ich schreibe meine eigene Sammlung, um die Binärkompatibilität zwischen C ++ 03 und C ++ 11 mit demselben Compiler zu gewährleisten (also keine STL, die wahrscheinlich kaputt gehen würde).

Tamás Szelei
quelle
13
+1! Gute Frage. Ich habe mich das Gleiche gefragt. Es ist einfach genug, etwas basierend auf Boost.Iterator zusammenzuschalten, aber es ist überraschend schwierig, nur eine Liste der Anforderungen zu finden, wenn Sie es von Grund auf neu implementieren.
Jalf
2
Denken Sie auch daran, dass Ihre Iteratoren beängstigend sein müssen. boost.org/doc/libs/1_55_0/doc/html/intrusive/…
alfC

Antworten:

232

http://www.cplusplus.com/reference/std/iterator/ verfügt über ein praktisches Diagramm, in dem die Spezifikationen von § 24.2.2 des C ++ 11-Standards aufgeführt sind. Grundsätzlich haben die Iteratoren Tags, die die gültigen Operationen beschreiben, und die Tags haben eine Hierarchie. Das Folgende ist rein symbolisch, diese Klassen existieren eigentlich nicht als solche.

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.

Sie können sich entweder spezialisieren std::iterator_traits<youriterator>oder dieselben Typedefs in den Iterator selbst einfügen oder von std::iterator(der diese Typedefs hat) erben . Ich bevorzuge die zweite Option, um Änderungen im stdNamespace zu vermeiden und um die Lesbarkeit zu gewährleisten, aber die meisten Menschen erben von 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
};

Beachten Sie die iterator_category sollte eine sein std::input_iterator_tag, std::output_iterator_tag, std::forward_iterator_tag, std::bidirectional_iterator_tag, oder std::random_access_iterator_tag, je nachdem , welche Anforderungen Ihre Iterator erfüllt. Je nach Ihrem Iterator können Sie wählen , sich zu spezialisieren std::next, std::prev, std::advance, und std::distanceauch, aber dies wird selten benötigt. In äußerst seltenen Fällen möchten Sie sich vielleicht spezialisieren std::beginund std::end.

Ihr Container sollte wahrscheinlich auch einen haben const_iterator, der ein (möglicherweise veränderbarer) Iterator für konstante Daten ist, der Ihrem ähnlich ist, iteratoraußer dass er implizit aus a konstruierbar sein iteratorsollte und Benutzer die Daten nicht ändern können sollten. Es ist üblich, dass sein interner Zeiger ein Zeiger auf nicht konstante Daten ist und von diesen iteratorgeerbt wurde const_iterator, um die Codeduplizierung zu minimieren.

Mein Beitrag beim Schreiben Ihres eigenen STL-Containers enthält einen vollständigeren Container / Iterator-Prototyp.

Mooing Duck
quelle
2
std::iterator_traitsSie können die Typedefs nicht nur spezialisieren oder selbst definieren, sondern auch einfach ableiten std::iterator, wodurch diese für Sie abhängig von den Vorlagenparametern definiert werden.
Christian Rau
3
@LokiAstari: Die vollständige Dokumentation ist ziemlich umfangreich (40 Seiten im Entwurf) und nicht im Rahmen des Stapelüberlaufs. Ich habe jedoch weitere Informationen zu den Iterator-Tags und hinzugefügt const_iterator. Was fehlte meinem Beitrag noch? Sie scheinen zu implizieren, dass der Klasse noch mehr hinzugefügt werden muss, aber die Frage bezieht sich speziell auf die Implementierung von Iteratoren.
Mooing Duck
5
std::iteratorwurde vorgeschlagen, in C ++ 17 veraltet zu sein ; es war nicht so, aber ich würde mich nicht darauf verlassen, dass es noch viel länger da ist.
Einpoklum
2
Ein Update zu @ einpoklums Kommentar: std::iteratorwar doch veraltet.
Scry
1
@ JonathanLee: Wow, das operator boolist unglaublich gefährlich. Jemand wird versuchen, damit das Ende eines Bereichs zu erkennen while(it++), aber alles, was wirklich überprüft wird, ist, ob der Iterator mit einem Parameter erstellt wurde.
Mooing Duck
16

Die iterator_facade- Dokumentation von Boost.Iterator bietet ein nettes Tutorial zum Implementieren von Iteratoren für eine verknüpfte Liste. Könnten Sie dies als Ausgangspunkt für die Erstellung eines Iterators mit wahlfreiem Zugriff über Ihrem Container verwenden?

Wenn nichts anderes, können Sie sich die von iterator_facadeund bereitgestellten Elementfunktionen und Typedefs ansehen und sie als Ausgangspunkt für die Erstellung Ihrer eigenen verwenden.

Michael Kristofik
quelle
10

Hier ist ein Beispiel eines Rohzeiger-Iterators.

Sie sollten keine Iteratorklasse verwenden, um mit rohen Zeigern zu arbeiten!

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

Problemumgehung auf Basis des Raw-Zeigerbereichs. Bitte korrigieren Sie mich, wenn es einen besseren Weg gibt, eine bereichsbasierte Schleife aus dem Rohzeiger zu erstellen.

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

Und einfacher 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);
    }
}
Valdemar_Rudolfovich
quelle
5

Zunächst können Sie hier eine Liste der verschiedenen Operationen suchen , die die einzelnen Iteratortypen unterstützen müssen.

Wenn Sie Ihre Iterator-Klasse erstellt haben, müssen Sie sich entweder darauf spezialisieren std::iterator_traitsund einige erforderliche typedefs (wie iterator_categoryoder value_type) bereitstellen oder sie alternativ ableiten std::iterator, wodurch die typedeffür Sie erforderlichen s definiert werden und daher mit der Standardeinstellung verwendet werden können std::iterator_traits.

Haftungsausschluss: Ich weiß, dass einige Leute nicht so cplusplus.comsehr mögen , aber sie liefern einige wirklich nützliche Informationen dazu.

Christian Rau
quelle
Ich verstehe den Streit zwischen cplusplus und cppreference wirklich nicht, sie sind beide gut und es fehlen viele Dinge. C ++ ist jedoch die einzige Sprache, in der die Implementierung von Standardbibliotheksiteratoren eine Höllen-XD ist. Meistens ist es einfacher, eine Wrapper-Klasse über einen STL-Container zu schreiben, als einen Iterator XD
CoffeDeveloper
@GameDeveloper Überprüfen Sie diese Vorlagenbibliothek, die ich für die Implementierung von Iteratoren geschrieben habe: github.com/VinGarcia/Simple-Iterator-Template . Es ist sehr einfach und erfordert nur etwa 10 Codezeilen, um einen Iterator zu schreiben.
VinGarcia
Schöne Klasse, ich weiß das zu schätzen, es lohnt sich wahrscheinlich, sie zu portieren, um sie auch mit Nicht-STL-Containern (EA_STL, UE4) zu kompilieren. :)
CoffeDeveloper
Wie auch immer, wenn der einzige Grund ist, dass cplusplus.com einige wirklich nützliche Informationen liefert, bietet cppreference.com weitere nützlichere Informationen ...
LF
@LF Dann können Sie in die Vergangenheit reisen und diese Informationen zur Version 2011 der Website hinzufügen. ;-)
Christian Rau
3

Ich war / bin aus verschiedenen Gründen im selben Boot wie Sie (teils lehrreich, teils eingeschränkt). Ich musste alle Container der Standardbibliothek neu schreiben und die Container mussten dem Standard entsprechen. Das heißt, wenn ich meinen Container gegen die stl- Version austausche , funktioniert der Code genauso. Was auch bedeutete, dass ich die Iteratoren neu schreiben musste.

Wie auch immer, ich sah EASTL an . Abgesehen davon, dass ich eine Menge über Container gelernt habe, die ich die ganze Zeit mit den stl- Containern oder durch meine Grundstudiengänge nie gelernt habe. Der Hauptgrund ist, dass EASTL besser lesbar ist als das stl- Gegenstück (ich fand, dass dies einfach auf das Fehlen aller Makros und den einfachen Codierungsstil zurückzuführen ist). Es gibt einige eklige Dinge (wie #ifdefs für Ausnahmen), aber nichts, was Sie überwältigen könnte.

Wie bereits erwähnt, lesen Sie die Referenz von cplusplus.com zu Iteratoren und Containern.

Samaursa
quelle
3

Ich habe versucht, das Problem zu lösen, dass mehrere verschiedene Textarrays durchlaufen werden können, die alle in einer großen speicherresidenten Datenbank gespeichert sind struct.

Das Folgende wurde mit Visual Studio 2017 Community Edition in einer MFC-Testanwendung erarbeitet. Ich füge dies als Beispiel hinzu, da dieses Posting eines von mehreren war, auf die ich gestoßen bin und die Hilfe lieferten, die aber für meine Bedürfnisse immer noch nicht ausreichten.

Die structDaten, die die speicherresidenten Daten enthielten, sahen ungefähr so ​​aus. Ich habe die meisten Elemente der Kürze halber entfernt und auch die verwendeten Präprozessordefinitionen nicht berücksichtigt (das verwendete SDK ist sowohl für C als auch für C ++ und alt).

Ich war daran interessiert, Iteratoren für die verschiedenen WCHARzweidimensionalen Arrays zu haben, die Textzeichenfolgen für die Mnemonik enthielten.

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;

Der aktuelle Ansatz besteht darin, eine Vorlage zu verwenden, um eine Proxy-Klasse für jedes der Arrays zu definieren und dann eine einzelne Iterator-Klasse zu haben, die zum Iterieren über ein bestimmtes Array verwendet werden kann, indem ein Proxy-Objekt verwendet wird, das das Array darstellt.

Eine Kopie der speicherresidenten Daten wird in einem Objekt gespeichert, das das Lesen und Schreiben der speicherresidenten Daten von / auf die Festplatte übernimmt. Diese Klasse CFileParaenthält die Proxy-Klasse mit Vorlagen ( MnemonicIteratorDimSizeund die Unterklasse, von der sie abgeleitet ist MnemonicIteratorDimSizeBase) und die Iterator-Klasse MnemonicIterator.

Das erstellte Proxy-Objekt wird an ein Iterator-Objekt angehängt, das über eine von einer Basisklasse beschriebene Schnittstelle, von der alle Proxy-Klassen abgeleitet sind, auf die erforderlichen Informationen zugreift. Das Ergebnis ist ein einzelner Typ von Iteratorklasse, der mit mehreren verschiedenen Proxy-Klassen verwendet werden kann, da die verschiedenen Proxy-Klassen alle dieselbe Schnittstelle, die Schnittstelle der Proxy-Basisklasse, verfügbar machen.

Das erste war, eine Reihe von Bezeichnern zu erstellen, die einer Klassenfactory zur Verfügung gestellt wurden, um das spezifische Proxy-Objekt für diesen Mnemoniktyp zu generieren. Diese Kennungen werden als Teil der Benutzeroberfläche verwendet, um die bestimmten Bereitstellungsdaten zu identifizieren, die der Benutzer sehen und möglicherweise ändern möchte.

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;

Die Proxy-Klasse

Die Proxy-Klasse mit Vorlagen und ihre Basisklasse lauten wie folgt. Ich musste verschiedene Arten von wchar_tText-String-Arrays unterbringen . Die zweidimensionalen Arrays hatten je nach Art (Zweck) der Mnemonik unterschiedliche Anzahlen von Mnemoniken, und die verschiedenen Arten von Mnemoniken hatten unterschiedliche maximale Längen und variierten zwischen fünf Textzeichen und zwanzig Textzeichen. Vorlagen für die abgeleitete Proxy-Klasse passten natürlich zu der Vorlage, die die maximale Anzahl von Zeichen in jeder Mnemonik erfordert. Nachdem das Proxy-Objekt erstellt wurde, verwenden wir die SetRange()Methode, um das tatsächliche Mnemonik-Array und seinen Bereich anzugeben.

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

};

Die Iterator-Klasse

Die Iteratorklasse selbst lautet wie folgt. Diese Klasse bietet nur grundlegende Forward-Iterator-Funktionen, die derzeit nur benötigt werden. Ich gehe jedoch davon aus, dass sich dies ändern oder erweitern wird, wenn ich etwas Zusätzliches benötige.

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

Die Proxy-Objekt-Factory bestimmt anhand der Mnemonik-ID, welches Objekt erstellt werden soll. Das Proxy-Objekt wird erstellt und der zurückgegebene Zeiger ist der Standard-Basisklassentyp, um eine einheitliche Schnittstelle zu erhalten, unabhängig davon, auf welche der verschiedenen Mnemonikabschnitte zugegriffen wird. Die SetRange()Methode wird verwendet, um dem Proxy-Objekt die spezifischen Array-Elemente anzugeben, die der Proxy darstellt, und den Bereich der Array-Elemente.

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

Verwenden der Proxy-Klasse und des Iterators

Die Proxy-Klasse und ihr Iterator werden wie in der folgenden Schleife gezeigt verwendet, um ein CListCtrlObjekt mit einer Liste von Mnemonics zu füllen . Ich benutze es, std::unique_ptrdamit std::unique_ptrder Speicher bereinigt wird , wenn die Proxy-Klasse, die ich nicht mehr benötige und die nicht mehr funktioniert.

Mit diesem Quellcode wird ein Proxy-Objekt für das Array erstellt, das structdem angegebenen Mnemonik-Bezeichner entspricht. Anschließend wird ein Iterator für dieses Objekt erstellt, ein Bereich forzum Ausfüllen des CListCtrlSteuerelements verwendet und anschließend bereinigt. Dies sind alles wchar_tRohtextzeichenfolgen, die genau der Anzahl der Array-Elemente entsprechen können. Daher kopieren wir die Zeichenfolge in einen temporären Puffer, um sicherzustellen, dass der Text mit Null abgeschlossen ist.

    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++;
    }
Richard Chambers
quelle
1

Und jetzt ein Schlüsseliterator für die bereichsbasierte for-Schleife.

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

Verwendungszweck:

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

Das habe ich gesucht. Aber niemand hatte es, wie es scheint.

Sie erhalten meine OCD-Code-Ausrichtung als Bonus.

Schreiben Sie als Übung Ihre eigenen für values(my_map)

Gabriel
quelle