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";
}
It works this way because the
Tracer()
is a temporary (rvalue) that can not bind to the non-const reference inoperator<<(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
orTracer() << 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.First of all note that
operator<<
which takesconst char*
as argument is a non-member function. And there exists a member function which takesvoid const*
as argument.In your code, the expression
Trace() << "xyz"
can be invoke only member functions, becauseTrace()
creates a temporay, which cannot bind to the first parameter of the non-memberoperator<<
functions, as these functions take the first argument asstd::ostream&
which is non-const reference. SoTrace() << "xyz"
resolves to memberoperator<<
which takesvoid*
as argument, which prints the address!My advices:
std::ostrstream
is deprecated anyway).operator<<
Here is one example:
Now you can use it as:
Output:
Live Demo