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)?
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).
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
.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.
The
struct
containing 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).What I was interested in doing is having iterators for the various
WCHAR
two dimensional arrays which contained text strings for mnemonics.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
.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.
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. 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 theSetRange()
method to specify the actual mnemonic array and its range.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.
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.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. I am usingstd::unique_ptr
so that when the proxy class i not longer needed and thestd::unique_ptr
goes out of scope, the memory will be cleaned up.What this source code does is to create a proxy object for the array within the
struct
which corresponds to the specified mnemonic identifier. It then creates an iterator for that object, uses a rangedfor
to fill in theCListCtrl
control and then cleans up. These are all rawwchar_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.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 stl version, the code would work the same. Which also meant that I had to re-write the iterators.
Anyway, I looked at EASTL. Apart from learning a ton about containers that I never learned all this time using the stl containers or through my undergraduate courses. The main reason is that EASTL is more readable than the stl counterpart (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.
As others mentioned, look at cplusplus.com's reference on iterators and containers.
Here is sample of raw pointer iterator.
You shouldn't use iterator class to work with raw pointers!
Raw pointer range based loop workaround. Please, correct me, if there is better way to make range based loop from raw pointer.
And simple test
The iterator_facade documentation from 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?
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.Thomas Becker wrote a useful article on the subject here.
There was also this (perhaps simpler) approach that appeared previously on SO: How to correctly implement custom iterators and const_iterators?
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.
You can either specialize
std::iterator_traits<youriterator>
, or put the same typedefs in the iterator itself, or inherit fromstd::iterator
(which has these typedefs). I prefer the second option, to avoid changing things in thestd
namespace, and for readability, but most people inherit fromstd::iterator
.Note the iterator_category should be one of
std::input_iterator_tag
,std::output_iterator_tag
,std::forward_iterator_tag
,std::bidirectional_iterator_tag
, orstd::random_access_iterator_tag
, depending on which requirements your iterator satisfies. Depending on your iterator, you may choose to specializestd::next
,std::prev
,std::advance
, andstd::distance
as well, but this is rarely needed. In extremely rare cases you may wish to specializestd::begin
andstd::end
.Your container should probably also have a
const_iterator
, which is a (possibly mutable) iterator to constant data that is similar to youriterator
except it should be implicitly constructable from aiterator
and users should be unable to modify the data. It is common for its internal pointer to be a pointer to non-constant data, and haveiterator
inherit fromconst_iterator
so as to minimize code duplication.My post at Writing your own STL Container has a more complete container/iterator prototype.