views:

929

answers:

3

I'm having a hard time using std::string::iterators in C++. This code compiles fine (still not getting correct output, but that's my fault: TODO, fix algorithm) in Dev-C++, and I don't get runtime errors. The error is with Visual Studio Express 2008 C++, where I'm getting an error pointing to < xstring>: "Expression: string iterator not dereferencable," and points to line 112 of the < xstring> file.

My debugging tells me I might be trying to dereference past the end of the sentence input, but I can't see where. Can anyone shed some light?

std::string wordWrap(std::string sentence, int width)
{    
    std::string::iterator it = sentence.begin();

    //remember how long next word is
    int nextWordLength = 0;
    int distanceFromWidth = width;

    while (it < sentence.end())
    {
       while (*it != ' ' && it != sentence.end())
       {
          nextWordLength++;
          distanceFromWidth--;
          it++;
       }

       if (nextWordLength > distanceFromWidth)
       {
          *it = '\n';
          distanceFromWidth = width;
          nextWordLength = 0;
       }

       //skip the space
       it++;

   }

   return sentence;    
}
+4  A: 
while (*it != ' ' && it != sentence.end())

changes to

while (it != sentence.end() && *it != ' ')

so the second expression isn't evaluated if the first if false.

   if (nextWordLength > distanceFromWidth)

should probably change to

   if (it == sentence.end())
         break;
   if (nextWordLength > distanceFromWidth)
Adrian Panasiuk
+6  A: 

Firstly, use operator!=() on iterators, not operator<():

while (it != sentence.end())


Secondly, this is backwards: while (*it != ' ' && it != sentence.end())

You do something with the iterator, than check if the iterator is valid. Rather, you should check if it's valid first:

while (it != sentence.end() && *it != ' ')


Thirdly, you should use ++iterator over iterator++, though this isn't related to your crashing.


Fourth, a main issue is here:

*it = '\n';

Because of the preceeding check, while (it != sentence.end(), it's possible to reach that iterator dereference while being at the end. A fix would be to do this:

if (it != sentence.end() && nextWordLength > distanceFromWidth)

So now if you have reached the end, you stop.


After fixing the previous issue, now the only problem is this:

//skip the space
++it;

This assumes that the character you are skipping is in fact a space. But what about the end of the string? Run this function with this string:

"a test string " // <- space at end

And it will succeed; it skips the space, putting the iterator at end(), the loop exits and success.

However, without the space it will crash, because you have reached the end, and are skipping past the end. To fix, add a check:

//skip the space
if (it != sentence.end())
{
    ++it;
}


Resulting in this final code:

std::string wordWrap(std::string sentence, int width)
{    
    std::string::iterator it = sentence.begin();

    //remember how long next word is
    int nextWordLength = 0;
    int distanceFromWidth = width;

    while (it != sentence.end())
    {
     while (it != sentence.end() && *it != ' ')
     {
      nextWordLength++;
      distanceFromWidth--;
      ++it;
     }

     if (it != sentence.end() && nextWordLength > distanceFromWidth)
     {
      *it = '\n';
      distanceFromWidth = width;
      nextWordLength = 0;
     }

     //skip the space
     if (it != sentence.end())
     {
      ++it;
     }

    }

    return sentence;    
}


You might notice this seems like it has a lot of redundant checks. This can be fixed:

std::string wordWrap(std::string sentence, int width)
{    
    std::string::iterator it = sentence.begin();

    //remember how long next word is
    int nextWordLength = 0;
    int distanceFromWidth = width;

    while (it != sentence.end())
    {
     while (*it != ' ')
     {
      nextWordLength++;
      distanceFromWidth--;

      ++it;

      // check if done
      if (it == sentence.end())
      {
       return sentence;
      }
     }

     if (nextWordLength > distanceFromWidth)
     {
      *it = '\n';
      distanceFromWidth = width;
      nextWordLength = 0;
     }

     //skip the space
     ++it;
    }

    return sentence;    
}


Hopefully that helps!

GMan
+1. I have corrected the code in point 2 to make it coincide with the complete block of code and the explanation.
David Rodríguez - dribeas
Thank you. I am aware of the ++it (so the compiler knows to copy away the extra iterator), and of the use of !=. I was getting errors compiling with !=, and I gave myself a break since I know strings are contiguous. I would not do the same with anything like a stack. A question: In your code, would it not be better to throw an exception if you got outside the loop? In your solution, you just return. That seems like it could mask some serious problems.
Hooked
I'm not sure I understand what out mean by outside the loop. The above code is safe and shouldn't need to throw any exceptions.
GMan
A: 

Almost certainly your error is the result of:

*it = '\n';

Since in the preceding while loop one of your stopping conditions is:

it != sentence.end()

If it == sentence.end(), then *it = '\n' won't fly

There are more errors, but that's the one that's causing your current problem.

csj