Thread: substr not behaving how I expect

  1. #1
    verbose cat
    Join Date
    Jun 2003
    Posts
    209

    substr not behaving how I expect

    I don't know if I am overlooking or misunderstanding something, but I am seeing some strange behavior.

    I am writing a text based game and have a Player class that is a base for a ConsolePlayer, NetworkPlayer and AIPlayer. I have included a public stringstream object called print so any function can pass data to the player via the familiar insertion operators, i.e.
    Code:
    player.print << "There were " << totalhearts << " hearts." << endl;
    If anyone knows of a better way to handle this, I'm all ears!

    My design is that the player objects will all be updated every pulse, and a printStream() function will be called during these updates to extract the output from the stringstream and deliver it to the appropriate mechanism (cout for ConsolePlayer, a send() or queue() function for NetworkPlayer, and push_back() onto a vector for AIPlayer so it can sort through the lines as needed). The extraction process will be the same regardless of the type, and a string will be sent to a function that the child class must overload.

    Also, like cout, I only want to send full lines (ending with a newline '\n') except in special circumstances which are outside the scope of this function.

    Here is my code:
    Code:
    #include <string>
    #include <sstream>
    #include <iostream>
    #include <iomanip>
    
    using namespace std;
    
    void printStream(stringstream &print);
    
    int main()
    {
        // create a string stream
        stringstream teststream;
        
        teststream << "01 one 789" << endl
                   << "01 two 789" << endl
                   << "0 three 89" << endl
                   << "01 four 89" << endl;
               
        // try and print it with the function already written
        printStream(teststream);
    
        return 0;
    }
    
    
    void printStream(stringstream &print)
    {
        // if the print stream is empty, we're done
        if ( print.str().empty() )
            return;
          
        string working(print.str());
        unsigned int begin = 0;
        unsigned int end = 0;
        unsigned int size = working.size();
        
        while ( (end = working.find('\n', begin)) != std::string::npos )
        {
            ++end; // move to one past the \n, substr will then print the \n 
                   // and ignore the char one-past it and begin needs to move
                   // to one-past the \n so we don't match the same \n again
            std::cout << "{b" << setw(3) << begin 
                      << ",e" << setw(3) << end 
                      << ",s" << setw(3) << size << "} "
                      << working.substr(begin, end);
            begin = end;
        }
    
        cout << "Begin: " << begin << endl
             << "End:   " << end   << endl
             << "Size:  " << size  << endl;
    
        if (begin == 0) // didn't find a '\n', so didn't print anything
        {
            cout << "No \\n's found." << endl;
            return;
        }
        
        print.clear();     // clear the stream
        if (begin != size) // if we didn't print the whole thing
            print.str(working.substr(begin)); //assign the remainder to the stream
        
        cout << endl << "string stream ends up as follows:" << endl
             << ">" << print.str() << "<" << endl;
        
        return;
    }
    This function is copied exactly from the program, including debug output (like printing the begin, end and size before each line), except that in the class the stringstream is not passed to the function because both are part of the class. This function does the exact same thing in this test program as it does when I compile the skeleton of the game I have so far, so I don't believe the problem is in anything other than this function. I also stripped all the std::'s from the std items and included the using namespace std; just so I could focus on the logic in this simple test program and not have to wade through all the std::'s.

    The cout in the while loop is there only for testing, it will be replaced with a call to the function that must be overloaded by the child classes and will take a single string representing the line of output that was extracted.

    I am compiling and running this via g++ version 3.4.4 under cygwin (WinXP). Here is the output I get:
    Code:
    Asha$ sstest
    {b  0,e 11,s 44} 01 one 789
    {b 11,e 22,s 44} 01 two 789
    0 three 89
    {b 22,e 33,s 44} 0 three 89
    01 four 89
    {b 33,e 44,s 44} 01 four 89
    Begin: 44
    End:   4294967295
    Size:  44
    
    string stream ends up as follows:
    >01 one 789
    01 two 789
    0 three 89
    01 four 89
    <
    
    Asha$
    As you can see, even though begin and end are pointing to the appropriate places in the string, working.substr(begin, end) is returning far more than it should. Line one is printed just fine, but then lines two and three are printed, and then lines three and four, and so on. I don't understand why this is happening.

    As an added bonus, print.clear(); apparently fails to do anything at all. I am using it exactly as cppreference.com shows. I replaced it with print.str(""); which works but seems like a kludge to me (though I guess the stringstream probably does the same thing as .clear() before assigning the empty string).

    I can't see any reason why this isn't working right. What am I missing?
    Last edited by jEssYcAt; 08-13-2007 at 03:38 AM. Reason: cleaned up a little
    abachler: "A great programmer never stops optimizing a piece of code until it consists of nothing but preprocessor directives and comments "

  2. #2
    Registered User hk_mp5kpdw's Avatar
    Join Date
    Jan 2002
    Location
    Northern Virginia/Washington DC Metropolitan Area
    Posts
    3,817
    Don't have a lot of time right now, maybe in a couple more hours, but for now:

    Code:
    unsigned int begin = 0;
    unsigned int end = 0;
    unsigned int size = working.size();
    Those should all be:
    Code:
    string::size_type begin = 0;
    string::size_type end = 0;
    string::size_type size = working.size();
    stringstreams, just like other streams (cin, etc...) can go into an error state which must be cleared for any further action to be taken on the stream, that's what the clear member function does. It does not "clear" the stream of data, it only resets the error flag. The other method you described, print.str(""), does reset the data in the stream (it does feel a bit kludgy to me as well but it does work.

    If your data being printed is going to include newline characters in it, then could you just use getline in a loop to extract the necessary data in appropriate chunks and then print it? It would seem much easier to do it that way.
    "Owners of dogs will have noticed that, if you provide them with food and water and shelter and affection, they will think you are god. Whereas owners of cats are compelled to realize that, if you provide them with food and water and shelter and affection, they draw the conclusion that they are gods."
    -Christopher Hitchens

  3. #3
    verbose cat
    Join Date
    Jun 2003
    Posts
    209
    Thank you so much for your response! I used getline() and now it works the way I expect.
    Code:
    void printStream2(stringstream &print)
    {
        string::size_type start;
       
        while ( (start = print.str().find('\n', 0)) != string::npos )
        {
            string working;
            
            getline(print, working);
            cout << working << endl; // replace with function to send the line
           
            working = print.str().substr(start + 1);  // +1 to skip the \n
        
            print.str(working);
        }    
        
        cout << endl << "string stream ends up as follows:" << endl 
             << ">" << print.str() << "<" << endl;
    }
    I'm still curious though why the .substr() function was giving such strange results before. Even changing the types from unsigned int to string::size_type didn't change anything. And since I am still using .substr() in my revised version, I am a bit worried that behavior might pop up again...?
    abachler: "A great programmer never stops optimizing a piece of code until it consists of nothing but preprocessor directives and comments "

  4. #4
    Registered User hk_mp5kpdw's Avatar
    Join Date
    Jan 2002
    Location
    Northern Virginia/Washington DC Metropolitan Area
    Posts
    3,817
    I would think that this is all you need:
    Code:
    void printStream2(stringstream &print)
    {
        string working;
        while ( getline(print,working) )
        {
            cout << working << endl; // replace with function to send the line
        }
    }
    "Owners of dogs will have noticed that, if you provide them with food and water and shelter and affection, they will think you are god. Whereas owners of cats are compelled to realize that, if you provide them with food and water and shelter and affection, they draw the conclusion that they are gods."
    -Christopher Hitchens

  5. #5
    verbose cat
    Join Date
    Jun 2003
    Posts
    209
    I did try that simple loop first as a proof, but without checking to see if there is a newline in the stringstream it kept crashing. My assumption is that getline() is looking for a newline as well, and if a stringstream doesn't contain a newline character, it runs past the end of the stream and dives into unknown territory.

    I only want to output complete lines, and the functions that send output to the stream are not required to end each line with a newline or endl. Some may want to output different parts of a line during different pulses and still have it all appear as one line of text, or output part of a line and pass execution to another function that will finish the line, etc.

    I also want to remove what has been output from the stringstream. If I don't remove data, it could become quite large over the course of the game. Several players in a long game would generate a TON of output, none of which is useful to the game after it has been sent to the player.

    The final cout was just debug info and will be removed from the final version.
    abachler: "A great programmer never stops optimizing a piece of code until it consists of nothing but preprocessor directives and comments "

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. substr problems
    By spudval in forum C++ Programming
    Replies: 4
    Last Post: 06-26-2007, 05:48 AM
  2. using expect script in C program
    By nitinmhetre in forum Linux Programming
    Replies: 1
    Last Post: 12-21-2006, 08:25 AM
  3. Substr
    By mhenderson in forum C Programming
    Replies: 4
    Last Post: 08-04-2006, 01:44 AM
  4. C++ calling an expect module
    By 15332000 in forum C++ Programming
    Replies: 0
    Last Post: 09-03-2004, 01:00 PM
  5. using substr
    By riley03 in forum C++ Programming
    Replies: 2
    Last Post: 02-24-2002, 06:32 PM