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.