tags:

views:

79

answers:

5
#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    string s = "Haven't got an idea why.";
    auto beg =  s.begin();
    auto end = s.end();
    while (beg < end)
    {
        cout << *beg << '\n';
        if (*beg == 'a')
        {//whithout if construct it works perfectly
            beg = s.erase(beg);
        }
        ++beg;
    }
    return 0;
}

Why if I erase one or more chars from this string this code breaks? I suppose it has something to do with returned iterator after erase operation being created at higher address than end iterator but I'm not sure and it surely isn't right behaviour. Or is it?

+4  A: 

The previous s.end() value stored in end is invalid after s.erase(). Hence, do not use it.

Daniel Daranas
@Daniel I'm sure you're right but it doesn't make much sense, does it? Why whould the end iterator be invalidated?
There is nothing we can do
@A-ha: It is invalidated by definition. The standard says so.
Marcelo Cantos
@A-ha What the standard says (as Marcelo correctly pointed out) has a good reason to be so. The iterator was looking at the end position of your string, and you deleted one character of it. It is only logical that its previous value is no longer valid. If you want to check against the new end, you must ask the container again (s.end()), not use a previously cached value.
Daniel Daranas
+8  A: 

There are several problems with this code.

  1. Don't cache the value of s.end(); it changes as you delete elements.
  2. Don't use beg < end. The idiomatic approach is to write beg != end. If you try to iterate past end, the result is undefined, and a debug version of the string library may deliberately crash your process, so it is meaningless to use <.
  3. The iterator returned from s.erase(beg) might be s.end(), in which case ++beg takes you past the end.

Here's a (I think) correct version:

int _tmain(int argc, _TCHAR* argv[])
{
    string s = "Haven't got an idea why.";
    for (auto beg = s.begin(); beg != s.end();)
    {
        cout << *beg << '\n';
        if (*beg == 'a')
        {//whithout if construct it works perfectly
            beg = s.erase(beg);
        }
        else
        {
            ++beg;
        }
    }
}

EDIT: I suggest accepting FredOverflow's answer. It is simpler and faster than the above.

Marcelo Cantos
+1  A: 

Note the semantics of a basic_string and it's iterators.

From www.ski.com/tech/stl

Note also that, according to the C++ standard, basic_string has very unusual iterator invalidation semantics. Iterators may be invalidated by swap, reserve, insert, and erase (and by functions that are equivalent to insert and/or erase, such as clear, resize, append, and replace). Additionally, however, the first call to any non-const member function, including the non-const version of begin() or operator[], may invalidate iterators. (The intent of these iterator invalidation rules is to give implementors greater freedom in implementation techniques.)

Also what happens if

 beg = s.erase(beg);

Returns an iterator equivalent to end()

Martin York
+5  A: 

Erasing elements one by one from vectors or strings has quadratic complexity. There are better solutions with linear complexity:

#include <string>
#include <algorithm>

int main()
{
    std::string s = "Haven't got an idea why.";
    s.erase(std::remove(s.begin(), s.end(), 'a'), s.end());
    std::cout << s << std::endl;
}
FredOverflow
+1  A: 

On calling erase operation, stored end iterator pointer becomes invalid. So, use s.end() function in while loop condition

Pardeep