Search code examples
c++strstream

ostrstream interprets constant string as pointer


I ran across this problem while cleaning up the debug macros of an old C/C++ application: We have a Tracer class inheriting from ostrstream (I know it's been deprecated since C++98, but this application was written in 1998!) which we use like this:

Tracer() << "some" << " message" << " here";

Now if the first value in the chain is a constant string like above, the result of calling ostrstream::str() on the Tracer (which is done in the destructor, inserting the result into a queue) contains a hexadecimal representation of the pointer to this string instead of the text. Thus the above statement would yield something like "0x401a37 message here". This didn't occur with the old macros as they always had a long (Thread ID) as the first value which has now been removed.

Stepping into it with gdb showed that for the first insertion, this calls operator<<(void const*) on the ostrstream, while the subsequent insertions call operator<< <...>(basic_ostream<...>&, char const*) (templating removed for readability).

Can somebody explain this behaviour? What would be a clean way to fix this? I have found an easy workaround, which is using << left as the first argument - is this safe? Are there better ways to do this?

Here's a minimized example:

#include <strstream>
#include <iostream>

using namespace std;

class Trace : public ostrstream {
    public:
        Trace();
        virtual ~Trace();
};

Trace::Trace() : ostrstream() {}
Trace::~Trace() {
    static_cast< ostrstream& >(*this) <<ends;
    char * text = ostrstream::str();
    cout << "MESSAGE: "<< text <<endl;
    delete[] text;
}

int main(){
    Trace() << "some" << " text" << " here";
    Trace() << left << "some" << " text" << " here";
    Trace() << 123 << " text" << " here";
}

Solution

  • It works this way because the Tracer() is a temporary (rvalue) that can not bind to the non-const reference in operator<<(basic_ostream<...>&, .

    However, you can call member functions like operator<<(void const*), because that doesn't require an lvalue.

    The member function then returns a reference to the stream object which can be used in calling the next operator<< in the sequence.

    Calling any member function this way, like Tracer() << left or Tracer() << flush, and thus "convert" the reference to an lvalue reference is quite safe.

    If you happen to have a C++11 compliant compiler, the standard library even contains an operator<<(basic_ostream<...>&&, which does this for you. In that case you don't need the workaround anymore.