4

Possible Duplicate:
Printing a string to a temporary stream object in C++
std::ostringstream printing the address of the c-string instead of its content

I'm trying to build up a string using stringstream, in the same way you'd use cout. This for something like a logging class. The issue I'm having is that if the first argument to the << operator is a string, when I subsequently print out that stringstream with the stringstream::str() call, I get a garbage address, and not the string. This only occurs with the FIRST string. Subsequent strings are fine. Numbers are always fine. Here's the code:

    // class I use to print out the stream
    class StreamWriter
    {
    public:
        StreamWriter()
        {}

        ~StreamWriter()
        {
            std::string myMessage = m_stringstream.str();
            std::cout << myMessage << std::endl;
        }

        std::stringstream m_stringstream;
    };

    // macro for simplification
    #define OSRDEBUG (StreamWriter().m_stringstream)

    // actual use
    OSRDEBUG << "Hello " << "my " << "name is Pris " << 123456;

    // output
    0x8054480my name is Pris 123456
    0x8054480my name is Pris 123456
    0x8054480my name is Pris 123456
    0x8054480my name is Pris 123456

Could anyone shed some light on what's going on, and how I could get around the issue?

EDIT: The following changes (in addition to padiablo's examples) works as well, maintaining the use of the class's destructor with the macro.

    // class I use to print out the stream
    class StreamWriter
    {
    public:
        StreamWriter()
        {   m_stringstream = new std::stringstream;   }

        ~StreamWriter()
        {
            std::string myMessage = m_stringstream.str();
            std::cout << myMessage << std::endl;
            delete m_stringstream;
        }

        std::stringstream * m_stringstream;
    };

    // macro for simplication
    #define OSRDEBUG *(StreamWriter().m_stringstream)

The original question still stands though, because it looks like it should work... and I think it's probably important to know when the times comes to put this into production-quality code.

Community
  • 1
  • 1
Prismatic
  • 3,338
  • 5
  • 36
  • 59
  • Any reason not to use `#define OSRDEBUG std::cout` ? – Matthieu M. Mar 09 '12 at 07:04
  • @Xeo: Ah nice catch, and only a few weeks before I asked mine too. Still, I've "redirected" it to mine since Nawaz did a fair job of explaining the root cause and give a work-around. – Matthieu M. Mar 09 '12 at 07:28

4 Answers4

5

The problem is indeed that the stream is a temporary.

Before C++11, there was no non-member operator<< overload that took an rvalue reference as the first parameter (aka, allowing writes to temporary streams).

As such, the only valid operator<< overloads were the members, since all non-member overloads take a non-const reference and as such will not bind to temporaries. One of those non-member overloads is the one responsible for printing C strings (aka char const*). Here's one of the member overloads:

basic_ostream<Ch, Traits>& operator<<(void* p);

And guess what your string literal liberally converts to. :)

After the first string, you get a normal reference back from the call to operator<<, which will then allow the non-member overloads to be viable.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • +1 for beating me to it. – Jon Purdy Mar 09 '12 at 07:03
  • @Jon: I couldn't believe this question stood "unanswered" for over an hour. :) – Xeo Mar 09 '12 at 07:04
  • @xeo: neither could I, but I have the benefit of having asked the question before ;) – Matthieu M. Mar 09 '12 at 07:07
  • @Matthieu: I had the feeling that this question has been asked before, and I think it was yours I was remembering. Oh well, seems like yours was a duplicate too! :) – Xeo Mar 09 '12 at 07:09
  • Well, in all fairness, we _did_ answer it, it's just our knowledge of C++ doesn't compete with yours :-) – paxdiablo Mar 09 '12 at 07:15
  • @pax: I didn't mean that in any negative sense, just that there was no definite answer wrt the non-member overloads, hence why I put "unanswered" in quotes. – Xeo Mar 09 '12 at 07:17
  • It's worth pointing out that in the classical (pre-standard) iostream, the `<<` operator for `char const*` was a member, and the usual solution to avoid all problems was to start by outputting an empty string (`""`). The standards committee broke this, and today, the "standard" solution is to use `std::ostringstream().flush()`; in this context, `flush()` is a no-op, but it returns an `ostream&`, on which all of the overloads work. – James Kanze Mar 09 '12 at 08:44
2

I honestly don't understand exactly what's going on (it has something to do with your StreamWriter instance being a temporary), but I see the same effect as paxdiablo described in both GCC and MSVC.

However, here's something that can work around the problem. Add the following helper to your StreamWriter class:

    ostream& get_ostream() {
        return m_stringstream;
    }

and change the macro to:

#define OSRDEBUG (StreamWriter().get_ostream())
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • Or just do `StreamWriter().flush()`. You already have a function which returns an `ostream&` and which is essentially a no-op otherwise. There's no reason to write a new one. – James Kanze Mar 09 '12 at 08:41
1

It appears to be a consequence of the way you're instantiating the object. I'm still investigating (and you may get a better answer in the meantime) but explicitly instantiating the object works fine:

#include <iostream>
#include <sstream>

class StreamWriter {
public:
    StreamWriter() {}
    ~StreamWriter() { std::cout << m_stringstream.str() << std::endl; }
    std::stringstream m_stringstream;
};

int main (void) {
    StreamWriter *sw = new StreamWriter();
    sw->m_stringstream << "Hello " << "my " << "name is Pris ";
    delete sw;
    return 0;
}

As does instantiating on the stack as well:

int main (void) {
    StreamWriter sw;
    sw.m_stringstream << "Hello " << "my " << "name is Pris ";
    return 0;
}

Both of those print out what you expect but the following simplification of your code does not:

int main (void) {
    StreamWriter().m_stringstream << "Hello " << "my " << "name is Pris ";
    return 0;
}

If you're just after a solution, you can probably get by with:

#define ORSDEBUG StreamWriter sw; sw.m_stringstream

and ensuring you scope-protect the command so that it doesn't try to create more then one sw, and also that it's destroyed at the correct time, same as your original attempt:

{ ORSDEBUG  << "Hello " << "my " << "name is Pris"; }
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Thank you for taking the time to investigate! I'm instantiating through the macro because I need the destructor to be called. In the sample code I gave, I just print the contents of the message to std::cout, but I intend to do other stuff in the destructor with the string at some point which is why I've set it up this way. – Prismatic Mar 09 '12 at 06:23
1

I have tried a couple of alternatives, and the only thing I got working is something like this:

#define OSRDEBUG(s)                 \
    do                              \
    {                               \
        StreamWriter writer;        \
        writer.m_stringstream << s; \
    } while (0)

OSRDEBUG("Hello " << "my " << "name is Pris " << 123456);

I have personally used the above construct for my own logging solutions many times, and seen it done by others as well.

I'm not good enough to know why your example doesn't work, but I guess it has something to do with temporaries not staying alive long enough.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621