77

Is there any way to check if an iterator (whether it is from a vector, a list, a deque...) is (still) dereferenceable, i.e. has not been invalidated?

I have been using try-catch, but is there a more direct way to do this?

Example: (which doesn't work)

list<int> l;
for (i = 1; i<10; i++) {
    l.push_back(i * 10);
}

itd = l.begin();
itd++;
if (something) {
    l.erase(itd);
}

/* now, in other place.. check if it points to somewhere meaningful */
if (itd != l.end())
{
    //  blablabla
}
huff
  • 1,954
  • 2
  • 28
  • 49
  • 7
    In C++, when you're just modifying the iterator and not using the value, you should always prefer `++itd` to `itd++`. – R Samuel Klatchko Jan 14 '10 at 08:47
  • 6
    After seeing your new code example, note that STL erase methods return the next iterator, which is a valid iterator (though it may be the end iterator). Therefore, to help keep itd valid, you could do this: if (something) { itd = l.erase(itd); } – Jason Govig Jan 14 '10 at 09:03
  • 1
    Also note that the reason R Samuel Klatchko advises always preferring pre-increment (++itd) over post-increment (itd++) is efficiency. Down to the differences in the implementation of the 2 operators, pre-increment will always be faster. It's also not just iterators it's relevant to, but any value that can be pre- and post-incremented. – boycy Sep 27 '11 at 09:27
  • possible duplicate of [How to check whether STL iterator points at anything?](http://stackoverflow.com/questions/824157/how-to-check-whether-stl-iterator-points-at-anything) – BЈовић Jan 22 '13 at 07:13
  • Note: The question linked to as duplicate has already been closed as duplicate of _this_ question (circular reference). – Damon Jan 22 '13 at 19:51
  • `libcstdc++` give the [implementation](https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/debug/safe_base.h) of the safe iterator and the basic functionalities are clarified in the comment. For example you can focus on the the function [if the iterator is singular](https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/src/c++11/debug.cc) – yuan Mar 26 '15 at 12:23

13 Answers13

67

I assume you mean "is an iterator valid," that it hasn't been invalidated due to changes to the container (e.g., inserting/erasing to/from a vector). In that case, no, you cannot determine if an iterator is (safely) dereferencable.

Jason Govig
  • 1,219
  • 8
  • 5
  • 12
    Although, I think it is time to introduce `Checked STL` into the fray: a checked stl goal is to catch iterators errors > use of invalid iterators or comparison of iterators from different containers among others. A trip by a checked stl should definitely be part of your test suite ;) – Matthieu M. Jan 14 '10 at 20:21
  • @Matthieu M : I dont think thats gonna happen in near future, as doing so would cost at the least, 1. keeping pointer to every iterator that refers the vector 2. When invalidating going through each element of the list Performance hawks will shoot that down from miles. :( – Ajeet Ganga Sep 09 '11 at 02:59
  • 5
    @Ajeet: Checked STL already exist, usually baked in the traditional STL but `#ifdef`ed out. It does cost, slowing the code, but MSVC's for example has 2 levels of checks, the first one being very accessible (the second one is definitely slow...) **Do** remember that this is obviously only for **Test** builds. – Matthieu M. Sep 09 '11 at 06:03
  • 2
    Well, the C++SL documents precisely for each and every container member function whether or not it invalidates iterators. Insofar, you cannot _check_ but you can _know_. – Damon Jan 22 '13 at 19:53
  • One of the wonderful things about undefined behaviour is that "fast and unpredictable" and "slow and loudly failing" are both valid implementations. – Caleth Sep 23 '22 at 11:49
27

As jdehaan said, if the iterator wasn't invalidated and points into a container, you can check by comparing it to container.end().

Note, however, that if the iterator is singular -- because it wasn't initialized or it became invalid after a mutating operation on the container (vector's iterators are invalidated when you increase the vector's capacity, for example) -- the only operation that you are allowed to perform on it is assignment. In other words, you can't check whether an iterator is singular or not.

std::vector<int>::iterator iter = vec.begin();
vec.resize(vec.capacity() + 1);
// iter is now singular, you may only perform assignment on it,
// there is no way in general to determine whether it is singular or not
avakar
  • 32,009
  • 9
  • 68
  • 103
15

Non-portable answer: Yes - in Visual Studio

Visual Studio's STL iterators have a "debugging" mode which do exactly this. You wouldn't want to enable this in ship builds (there is overhead) but useful in checked builds.

Read about it on VC10 here (this system can and in fact does change every release, so find the docs specific to your version).

Edit Also, I should add: debug iterators in visual studio are designed to immediately explode when you use them (instead undefined behavior); not to allow "querying" of their state.

Terry Mahaffey
  • 11,775
  • 1
  • 35
  • 44
  • 2
    Just as an addendum to this answer, LLVM, version 12.0, provides a [debug mode](https://libcxx.llvm.org/docs/DesignDocs/DebugMode.html) that can provide a similar debugging capability. It's enabled by using the `_LIBCPP_DEBUG` macro. Older versions of LLVM (like 11) also appear to have support for this. The necessary numeric setting of this macro seems dependent on the LLVM version however. – Louis Langholtz Aug 24 '20 at 21:39
9

Usually you test it by checking if it is different from the end(), like

if (it != container.end())
{
   // then dereference
}

Moreover using exception handling for replacing logic is bad in terms of design and performance. Your question is very good and it is definitively worth a replacement in your code. Exception handling like the names says shall only be used for rare unexpected issues.

jdehaan
  • 19,700
  • 6
  • 57
  • 97
  • 5
    So, when you destroy the element that the iterator is pointing to in a list, or an element situated before on a vector, the iterator then points to the end? I doesn't, in my case... (I will edit the question to be more clear) – huff Jan 14 '10 at 08:41
  • When deleting and inserting, *all* iterators and references might be destroyed. So then you better get new iterators before you continue. This is because a eg. a vector will sometimes have to reallocate all the memory before adding a new item. This will then ofcourse invalidate all pointers, references and iterators (which in most cases are much like pointers) – daramarak Jan 14 '10 at 08:46
  • @huff You have to read the API documentation of vector::erase and list::erase to understand the behavior. Further there are some gray areas here where the API was (is it still?) slightly different for Microsoft and GCC implementation of std::map::erase, if I can recall correctly. – amit kumar Jan 14 '10 at 08:48
  • @huff in that case all iterators become invalid. There are quite good books like Effective STL & More effective STL from C++ Guru Scott Meyers or other books from Herb Sutter that can explain what happens in detail. For some containers the erase returns an iterator so you can safely iterate further. – jdehaan Jan 14 '10 at 09:15
  • What do you mean by `container`? Is it `std::container`? Or you mean the original container? What if I don't have access to original container? – Chirag Arora Jun 20 '20 at 13:41
  • I meant a container as an abstraction, like `vector`, `list`, ... that provides iterators. In the case of the example in the code then `l.end()`... It has been a long time since I wrote that, I must also say tom me it looked unclear now :-) thanks for asking – jdehaan Jul 06 '20 at 09:24
7

Is there any way to check if a iterator (whether it is from a vector, a list, a deque...) is (still) dereferencable, i.e has not been invalidated ?

No, there isn't. Instead you need to control access to the container while your iterator exists, for example:

  • Your thread should not modify the container (invalidating the iterator) while it is still using an instantiated iterator for that container

  • If there's a risk that other threads might modify the container while your thread is iterating, then in order to make this scenario thread-safe your thread must acquire some kind of lock on the container (so that it prevents other threads from modifying the container while it's using an iterator)

Work-arounds like catching an exception won't work.

This is a specific instance of the more general problem, "can I test/detect whether a pointer is valid?", the answer to which is typically "no, you can't test for it: instead you have to manage all memory allocations and deletions in order to know whether any given pointer is still valid".

ChrisW
  • 54,973
  • 13
  • 116
  • 224
  • And in a multithreaded scenario, this will suck, won't it?: l.erase(itd); itd = l.end(); - And the other thread compares itd to l.end(). - Yeah, I know it's not perfect, but the chances of the other thread intervening after the erasing and before the assignation are so remote... eheheh :D – huff Jan 14 '10 at 09:02
  • If you write your own container (instead of using the STL's) then you might -- 1) Let the container track (remember) what iterator instances are currently constructed 2) Have the container's destructor set a flag in the instance of each iterator 3) Have the iterator's methods check that flag (to verify whether the container still exists before trying to access it) 4) Optionally do this in a thread-safe way 5) Also do something similar on other container modifications which may invalidate an iterator (e.g. deleting or adding an element in the container). – ChrisW Nov 28 '18 at 08:54
  • When I said "no", above, I meant when using the standard container implementations (which were designed to be especially fast, and not especially safe). – ChrisW Nov 28 '18 at 08:55
3

Trying and catching is not safe, you will not, or at least seldom throw if your iterator is "out of bounds".

what alemjerus say, an iterator can always be dereferenced. No matter what uglyness lies beneath. It is quite possible to iterate into other areas of memory and write to other areas that might keep other objects. I have been looking at code, watching variables change for no particular reason. That is a bug that is really hard to detect.

Also it is wise to remember that inserting and removing elements might potentially invalidate all references, pointers and iterators.

My best advice would be to keep you iterators under control, and always keep an "end" iterator at hand to be able to test if you are at the "end of the line" so to speak.

daramarak
  • 6,115
  • 1
  • 31
  • 50
  • 2
    With 'can be dereferenced' you probably mean: nobody will prevent you from doing it. However, undefined behavior will occur when dereferencing invalidated iterators. – xtofl Jan 14 '10 at 08:46
2

Is there any way to check if an iterator is dereferencable

Yes, with gcc debugging containers available as GNU extensions. For std::list you can use __gnu_debug::list instead. The following code will abort as soon as invalid iterator is attempted to be used. As debugging containers impose extra overhead they are intended only when debugging.

#include <debug/list>

int main() {
  __gnu_debug::list<int> l;
  for (int i = 1; i < 10; i++) {
    l.push_back(i * 10);
  }

  auto itd = l.begin();
  itd++;
  l.erase(itd);

  /* now, in other place.. check if itd points to somewhere meaningful */
  if (itd != l.end()) {
    //  blablabla
  }
}

$ ./a.out 
/usr/include/c++/7/debug/safe_iterator.h:552:
Error: attempt to compare a singular iterator to a past-the-end iterator.

Objects involved in the operation:
    iterator "lhs" @ 0x0x7ffda4c57fc0 {
      type = __gnu_debug::_Safe_iterator<std::_List_iterator<int>, std::__debug::list<int, std::allocator<int> > > (mutable iterator);
      state = singular;
      references sequence with type 'std::__debug::list<int, std::allocator<int> >' @ 0x0x7ffda4c57ff0
    }
    iterator "rhs" @ 0x0x7ffda4c580c0 {
      type = __gnu_debug::_Safe_iterator<std::_List_iterator<int>, std::__debug::list<int, std::allocator<int> > > (mutable iterator);
      state = past-the-end;
      references sequence with type 'std::__debug::list<int, std::allocator<int> >' @ 0x0x7ffda4c57ff0
    }
Aborted (core dumped)
ks1322
  • 33,961
  • 14
  • 109
  • 164
1

In some of the STL containers, the current iterator becomes invalid when you erase the current value of the iterator. This happens because the erase operation changes the internal memory structure of the container and increment operator on existing iterator points to an undefined locations.

When you do the following, iterator is incementented before it is passed to erase function.

if (something) l.erase(itd++);

lava
  • 1,945
  • 2
  • 14
  • 15
-1

The type of the parameters of the erase function of any std container (as you have listed in your question, i.e. whether it is from a vector, a list, a deque...) is always iterator of this container only.

This function uses the first given iterator to exclude from the container the element that this iterator points at and even those that follow. Some containers erase only one element for one iterator, and some other containers erase all elements followed by one iterator (including the element pointed by this iterator) to the end of the container. If the erase function receives two iterators, then the two elements, pointed by each iterator, are erased from the container and all the rest between them are erased from the container as well, but the point is that every iterator that is passed to the erase function of any std container becomes invalid! Also:

Each iterator that was pointing at some element that has been erased from the container becomes invalid, but it doesn't pass the end of the container!

This means that an iterator that was pointing at some element that has been erased from the container cannot be compared to container.end(). This iterator is invalid, and so it is not dereferencable, i.e. you cannot use neither the * nor -> operators, it is also not incrementable, i.e. you cannot use the ++ operator, and it is also not decrementable, i.e. you cannot use the -- operator.

It is also not comparable!!! I.E. you cannot even use neither == nor != operators

Actually you cannot use any operator that is declared and defined in the std iterator. You cannot do anything with this iterator, like null pointer.

Doing something with an invalid iterator immediately stops the program and even causes the program to crash and an assertion dialog window appears. There is no way to continue program no matter what options you choose, what buttons you click. You just can terminate the program and the process by clicking the Abort button.

You don't do anything else with an invalid iterator, unless you can either set it to the begin of the container, or just ignore it.

But before you decide what to do with an iterator, first you must know if this iterator is either invalid or not, if you call the erase function of the container you are using.

I have made by myself a function that checks, tests, knows and returns true whether a given iterator is either invalid or not. You can use the memcpy function to get the state of any object, item, structure, class and etc, and of course we always use the memset function at first to either clear or empty a new buffer, structure, class or any object or item:

bool IsNull(list<int>::iterator& i) //In your example, you have used list<int>, but if your container is not list, then you have to change this parameter to the type of the container you are using, if it is either a vector or deque, and also the type of the element inside the container if necessary.
{
    byte buffer[sizeof(i)];
    memset(buffer, 0, sizeof(i));
    memcpy(buffer, &i, sizeof(i));
    return *buffer == 0; //I found that the size of any iterator is 12 bytes long. I also found that if the first byte of the iterator that I copy to the buffer is zero, then the iterator is invalid. Otherwise it is valid. I like to call invalid iterators also as "null iterators".
}

I have already tested this function before I posted it there and found that this function is working for me.

I very hope that I have fully answered your question and also helped you very much!

  • Sorry, but this is just a set of meaningless anecdotes, topped off by nonsensical or harmful ideas. (A) `erase` does not remove "the two elements" at its input iterators; it#2 is past-end/exclusive. (B) That's what invalid iterators do on _your implementation at one time_; mine might never crash, might crash at exit, might throw a totally random `assert` from GTK+, _etc._... (B) Don't spread such direly unsafe ideas: that all iterators have the same size, that being all-0x00 is _somehow_ a sign of invalidity (& there's any point `memset`ing a buffer before `memcpy`ing over it all; _why_?)...no – underscore_d Feb 26 '16 at 01:36
-1

There is a way, but is ugly... you can use the std::distance function

  #include <algorithms>
  using namespace std
  auto distance_to_iter = distance(container.begin(), your_iter);
  auto distance_to_end = distance(container.begin(),container.end());
  
  bool is_your_iter_still_valid = distance_to_iter != distance_to_end;
aralce
  • 1
  • 1
  • This categorically does not work, as explained in other answers. – Konrad Rudolph May 24 '23 at 08:41
  • @KonradRudolph I saw the message you. Please, ciuld you please be more kind with @Ahmed_mag? – aralce May 25 '23 at 10:36
  • Let me explain. I do not normally contribute to stack overflow. I just read this thread because I needed an implementation just to do what is asked for in the first question. I did not find nothing about it, but I found that there are STL funtions on algorithms that return the same for end() iterator than for an imvalid iterator. So, when I solved my issue I just wanted to share the solution here when i first try to look for it. So, please be more humble and at least check it first – aralce May 25 '23 at 10:47
  • "I found that there are STL funtions on algorithms that return the same for end() iterator than for an imvalid iterator." No, you are mistaken. This does not work. It may *seem* to work sometimes, because it is [undefined behaviour](https://en.wikipedia.org/wiki/Undefined_behavior). But it does not work reliably, and it is **always** a bug. In C++ you cannot simply "try it" to find out if something works. You need to actually read and understand the official specification. There are countless discussions of this here on Stack Overflow and elsewhere on the internet. – Konrad Rudolph May 25 '23 at 11:53
  • I checked the distance function documentation and you are right. It has an undefined behavior for invalid iterators. Thanks for take the time to let me kniw I was wrong about it. I will try to take more care with the next functions I use – aralce May 25 '23 at 22:30
-1

Try this:

auto itd = l.begin();
//decrement the iterator
if (itd != l.end() && distance(l.begin(), itd) > 0 )
{
    advance(itd, -1);
}

advance(itd, 1);
if (itd != l.end() && distance(l.begin(), itd) > 0)
{
    advance(itd, -1);
}
//increment the iterator
itd = l.end();
if (itd != l.end() && distance( itd, l.end()) >1)
{
    advance(itd, 1);
}

advance(itd, -1);
if (itd != l.end() && distance(itd, l.end()) > 1)
{
    advance(itd, 1);
}
Ahmed_mag
  • 232
  • 2
  • 6
  • Like your answer, this one *also* does not work. And it does not seem to have anything to do with the question, even. Please stop spamming this question with answers, especially since (as already explained!) the original question *is not solvable* in conforming, portable C++. – Konrad Rudolph May 24 '23 at 16:37
  • @KonradRudolph I tried it and it is working, it increments and decrements the iterator whenever possible. – Ahmed_mag May 25 '23 at 13:00
  • The title of the question is "Checking if an iterator is valid" so: to check whether the iterator is valid for decrement use: if (itd != l.end() && distance(l.begin(), itd) > 0 ) for decrement use if (itd != l.end() && distance( itd, l.end()) >1) – Ahmed_mag May 25 '23 at 13:08
  • You fundamentally cannot use the approach "tried it and it is working" in C++ due to [undefined behaviour](https://en.wikipedia.org/wiki/Undefined_behavior). Your code happens to work for you by pure luck, and may break at any moment. The code is incorrect. And there is no correct solution, as explained in other answers, because that is how C++ is defined. – Konrad Rudolph May 25 '23 at 13:12
-2

use erase with increment :

   if (something) l.erase(itd++);

so you can test the validity of the iterator.

lsalamon
  • 7,998
  • 6
  • 50
  • 63
-3
if (iterator != container.end()) {
   iterator is dereferencable !
}

If your iterator doesnt equal container.end(), and is not dereferencable, youre doing something wrong.

Viktor Sehr
  • 12,825
  • 5
  • 58
  • 90