Iterators in the STL are badly designed - there are several major pitfalls with using them as a result of their design. However, I wanted STLplus to feel like the STL to make it easier to use, so felt I should use the iterator concept despite the problems associated with them. Some of the STLplus components therefore also use iterators.
The problems with STL iterators are:
The STLplus safe iterators were designed to solve the problems with the STL iterators. The following solutions have been implemented:
The following STLplus classes use safe iterators, all of them based on the safe_iterator class:
Here is the user interface to the safe_iterator class:
template<typename O, typename N> class stlplus::safe_iterator { public: // construct a null iterator safe_iterator(void); // construct a valid iterator from the owner node's master iterator safe_iterator(const master_iterator<O,N>&); // comparison - used to create comparison operators for putting // iterators into maps and hashes bool equal(const safe_iterator<O,N>& right) const; int compare(const safe_iterator<O,N>& right) const; // dereference N& operator*(void) const; N* operator->(void) const; // a null iterator is one that has not been initialised with a value yet // i.e. you just declared it but didn't assign to it bool null(void) const; // an end iterator is one that points to the end element of the list of nodes // in STL conventions this is one past the last valid element and must not be dereferenced bool end(void) const; // a valid iterator is one that can be dereferenced // i.e. non-null and non-end bool valid(void) const; // called by the owner class to check the rules void assert_valid(const O* owner) const; };
The void constructor ensures that an unassigned iterator is classified as a null iterator.
The second iterator is used internally by the container object to create a valid iterator.
Built-in comparison methods allow for comparison operators to be written for any iterator derived from safe_iterator, for example to test for the end condition in a for loop or any other conditional. They also allow iterators to be added to sets (the < operator) and hashes (the == operator).
The dereference operators are only legal on valid iterators - any attempt to dereference a null or end iterator will throw an exception.
The methods null(), end() and valid() allow the programmer to test the validity of an iterator before dereferencing it.
The assert_valid method is called by the owning container to check that the iterator belongs to it before using it. If not, it will throw the wrong_object exception. Also, if the owner needs to dereference the iterator, it will check whether it is valid and if not, throw either the null_dereference or end_dereference exception.