1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
In the chapter about containers (chapter ref(CONTAINERS)) it was noted that
containers own the information they contain. If they contain objects, then
those objects are destroyed once the containers are destroyed. As pointers are
not objects their use in containers is discouraged (STL's ti(unique_ptr) and
ti(shared_ptr) type objects may be used, though). Although discouraged, we
might be able to use pointer data types in specific contexts. In the following
class tt(StringPtr), an ordinary class is derived from the tt(std::vector)
container that uses tt(std::string *) as its data type:
verbinclude(-a examples/stringptrs/stringptr1.h)
This class needs a destructor: as the object stores string pointers, a
destructor is required to destroy the strings once the tt(StringPtr) object
itself is destroyed. Similarly, a copy constructor and an overloaded
assignment operator are required. Other members (in particular: constructors)
are not explicitly declared here as they are not relevant to this section's
topic.
Assume that we want to use the tt(sort) generic algorithm with
tt(StringPtr) objects. This algorithm (see section ref(SORT)) requires two
em(RandomAccessIterators). Although these iterators are available (via
tt(std::vector)'s tt(begin) and tt(end) members), they return iterators to
tt(std::string *)s. When comparing tt(string *) values the pointer values
instead of the content of the strings are compared, which is not what we want.
To remedy this, an internal type tt(StringPtr::iterator) is defined,
not returning iterators to pointers, but iterators to the em(objects) these
pointers point to. Once this tt(iterator) type is available, we can add the
following members to our tt(StringPtr) class interface, hiding the identically
named, but useless members of its base class:
verb( StringPtr::iterator begin(); // returns iterator to the first element
StringPtr::iterator end(); // returns iterator beyond the last
// element)
Since these two members return the (proper) iterators, the elements in a
tt(StringPtr) object can easily be sorted:
verb( int main()
{
StringPtr sp; // assume sp is somehow filled
sort(sp.begin(), sp.end()); // sp is now sorted
})
To make this work, a type tt(StringPtr::iterator) is defined. As suggested by
its type name, tt(iterator) is a nested type of tt(StringPtr). To use a
tt(StringPtr::iterator) in combination with the tt(sort) generic algorithm it
must be a ti(RandomAccessIterator), whose tt(value_type) is a
tt(std::string). Therefore, the iterator specifies:
verbinclude(//USING examples/stringptrs/stringptr.h)
Now we're ready to redesign tt(StringPtr)'s class interface. It offers
members returning (reverse) iterators, and a nested tt(iterator) class. Here
is its interface:
verbinclude(//STRINGPTR examples/stringptrs/stringptr.h)
Next we have a look at tt(StringPtr::iterator)'s characteristics:
itemization(
itt(iterator) defines tt(StringPtr) as its friend, so tt(iterator)'s
constructor can be private. Only the tt(StringPtr) class itself should be able
to construct iterators. Copy construction and iterator-assignment should be
possible, but that's possible by default and needs no specific declaration or
implementation. Furthermore, since an iterator is already provided by
tt(StringPtr)'s base class, we can use that iterator to access the information
stored in the tt(StringPtr) object.
it() tt(StringPtr::begin) and tt(StringPtr::end) may simply return
tt(iterator) objects. They are implemented like this:
verbinclude(//BEGEND examples/stringptrs/stringptr.h)
it() All of tt(iterator)'s remaining members are public. It's very easy to
implement them, mainly manipulating and dereferencing the available iterator
tt(d_current). A tt(RandomAccessIterator) requires a series of operators. They
usually have very simple implementations, and can often very well be
implemented inline:
itemization(
itt(iterator &operator++()); the pre-increment operator:
verbinclude(//PREINC examples/stringptrs/stringptr.h)
it() tt(iterator operator++(int)); the post-increment operator:
verbinclude(//POSTINC examples/stringptrs/stringptr.h)
itt(iterator &)ttNoCt(operator--()); the pre-decrement operator:
verbinclude(//PREDEC examples/stringptrs/stringptr.h)
it() ttNoCt(iterator operator--(int)); the post-decrement operator:
verbinclude(//POSTDEC examples/stringptrs/stringptr.h)
itt(iterator &operator=(iterator const &other)); the overloaded
assignment operator. Since tt(iterator) objects do not allocate
any memory themselves, the default assignment operator can be
used.
itt(bool operator==(iterator const &rhv) const); testing the equality
of two tt(iterator) objects:
verbinclude(//OPEQ examples/stringptrs/stringptr.h)
itt(auto operator<=>(iterator const &rhv) const); testing the
ordering of two tt(iterator) objects:
verbinclude(//OPSPACE examples/stringptrs/stringptr.h)
itt(int operator-(iterator const &rhv) const); returning the number of
elements between the element pointed to by the left-hand side
iterator and the right-hand side iterator (i.e., the value to add
to the left-hand side iterator to make it equal to the value of
the right-hand side iterator):
verbinclude(//OPSUB examples/stringptrs/stringptr.h)
itt(Type &operator*() const); returning a reference to the object to
which the current iterator points. With an tt(InputIterator) and
with all tt(const_iterators), the return type of this overloaded
operator should be tt(Type const &). This operator returns a
reference to a string. This string is obtained by dereferencing
the dereferenced tt(d_current) value. As tt(d_current) is an
iterator to tt(string *) elements, two dereference operations are
required to reach the string itself:
verbinclude(//OP* examples/stringptrs/stringptr.h)
itt(iterator operator+(int stepsize) const); this operator
advances the current iterator by tt(stepsize):
verbinclude(//OPADD examples/stringptrs/stringptr.h)
itt(iterator operator-(int stepsize) const); this operator
decreases the current iterator by tt(stepsize):
verbinclude(//OP- examples/stringptrs/stringptr.h)
itt(std::string *operator->() const) is an additionally added
operator. Here only one dereference operation is required,
returning a pointer to the string, allowing us to access the
members of a string via its pointer.
verbinclude(//OPARROW examples/stringptrs/stringptr.h)
it() Two more additionally added operators are tt(operator+=) and
tt(operator-=). They are not formally required by
tt(RandomAccessIterators), but they come in handy anyway:
verbinclude(//OPARITH examples/stringptrs/stringptr.h)
)
)
The interfaces required for other iterator types are simpler, requiring
only a subset of the interface required by a random access iterator. E.g.,
the forward iterator is never decremented and never incremented over arbitrary
step sizes. Consequently, in that case all decrement operators and
tt(operator+(int step)) can be omitted from the interface. Of course, the tag
to use would then be tt(std::forward_iterator_tag). The tags (and the set of
required operators) vary accordingly for the other iterator types.
|