stringstream, string, and char* conversion confusion

Graphics Noob picture Graphics Noob · Sep 3, 2009 · Viewed 165.4k times · Source

My question can be boiled down to, where does the string returned from stringstream.str().c_str() live in memory, and why can't it be assigned to a const char*?

This code example will explain it better than I can

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a string\n");

    string str(ss.str());

    const char* cstr1 = str.c_str();

    const char* cstr2 = ss.str().c_str();

    cout << cstr1   // Prints correctly
        << cstr2;   // ERROR, prints out garbage

    system("PAUSE");

    return 0;
}

The assumption that stringstream.str().c_str() could be assigned to a const char* led to a bug that took me a while to track down.

For bonus points, can anyone explain why replacing the cout statement with

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

prints the strings correctly?

I'm compiling in Visual Studio 2008.

Answer

sbi picture sbi · Sep 3, 2009

stringstream.str() returns a temporary string object that's destroyed at the end of the full expression. If you get a pointer to a C string from that (stringstream.str().c_str()), it will point to a string which is deleted where the statement ends. That's why your code prints garbage.

You could copy that temporary string object to some other string object and take the C string from that one:

const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();

Note that I made the temporary string const, because any changes to it might cause it to re-allocate and thus render cstr invalid. It is therefor safer to not to store the result of the call to str() at all and use cstr only until the end of the full expression:

use_c_str( stringstream.str().c_str() );

Of course, the latter might not be easy and copying might be too expensive. What you can do instead is to bind the temporary to a const reference. This will extend its lifetime to the lifetime of the reference:

{
  const std::string& tmp = stringstream.str();   
  const char* cstr = tmp.c_str();
}

IMO that's the best solution. Unfortunately it's not very well known.