Saeid
Saeid

Reputation: 4255

Force an exception when dereferencing an invalid iterator

Recently I have found a bug in a huge code because of dereferencing set.begin() while the set was empty. Is there a way (like setting a compiler flag) to force containers to throw an exception when dereferencing an invalid iterator like an empty set's .begin() or .end()?

std::set<int> s;
*s.begin(); // force this to throw an exception because std::set is empty

Upvotes: 0

Views: 1049

Answers (2)

Sam Varshavchik
Sam Varshavchik

Reputation: 118300

There's nothing about any particular iterator that announces, in some way, whether the iterator can be validly dereferenced. C++ is not a virtual machine-based code, like Java or C#, with the virtual machine keeping track of each object's validity.

It is possible that there might be compiler-specific options that enable additional run-time sanity checks. But since you haven't even identified your compiler, the answer here would simply be "check your compiler's documentation".

And if none of the options works for you, the answer will be "write it yourself". Based on a compile-time macro and by using certain coding conventions it would be possible, for example, to implement an iterator-compatible interface that performs additional sanity checks. For example, instead of declaring

std::set<int> set_of_ints;

and

std::set<int>::iterator b=set_of_ints.begin();

// Or something else that references std::set<int>::iterator

Declare and always use aliases:

typedef std::set<int> set_of_ints_t;

set_of_ints_t set_of_ints;

set_of_ints_t::iterator p=set_of_ints.begin();

... and so on. With this coding convention in place, it becomes easy to use a compile-time macro to enable additional sanity checks:

#ifndef DEBUG
    typedef std::set<int> set_of_ints_t;
#else
    typedef my_sanity_checked_set sets_of_ints_t;
#endif

With my_sanity_checked_set being a custom container that's interface-compatible with a std::set<int>, and with an iterator whose operators perform additional sanity checks on every operation (such as, for example, not incrementing or decrementing past the set's boundaries, dereferencing the end() value, etc...)

All this checking comes with extra overhead, of course. You would use this during development, then turn the whole thing off and compile using the native std::set for the release build. That's how it's done.

Upvotes: 1

Barry
Barry

Reputation: 302767

set.begin() will never throw an exception. I want to know what is the reason of that.

An empty container is a perfectly valid container, and it is perfectly reasonable to perform algorithms on them. It would be pretty surprising if:

if (std::find(c.begin(), c.end(), v) == c.end()) {
   // not present
}

worked fine for a container with 1 or more elements that didn't contain v, but threw an exception if it were empty! It's not exceptional for a container to be empty. That would be insane. That would require every programmer at every point to specially check emptiness as a special case - when it isn't really.

The rule is simply that dereferencing the end() iterator is undefined behavior. For empty containers, begin() == end() so that extends to begin() as well. It is up to your code to control those accesses.


Also is there a way (like setting a compiler flag) to force containers to throw an exception when dereferencing an invalid iterator like an empty set's .begin() or .end()?

Some implementations will assert if you try to dereference an invalid iterator (e.g. libstdc++). But you could always simply write a wrapper implementation that will do this for you:

#ifdef NDEBUG
    template <class T>
    using my_set = std::set<T>;
#else
    template <class T>
    class my_set {
        // implement your own set that carefully manages all the lifetimes
        // of its entries such that it's possible to check the validity
        // of them in the iterators, and then throw on bad dereference
    };
#endif

Upvotes: 2

Related Questions