问题
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";
}
回答1:
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.
回答2:
First of all note that operator<<
which takes const char*
as argument is a non-member function. And there exists a member function which takes void const*
as argument.
In your code, the expression Trace() << "xyz"
can be invoke only member functions, because Trace()
creates a temporay, which cannot bind to the first parameter of the non-member operator<<
functions, as these functions take the first argument as std::ostream&
which is non-const reference. So Trace() << "xyz"
resolves to member operator<<
which takes void*
as argument, which prints the address!
My advices:
- Don't inherit from stream class (
std::ostrstream
is deprecated anyway). - Rather write a simple wrapper over stream class and overload
operator<<
Here is one example:
#include <sstream> //for std::ostringstream
struct Trace
{
std::ostringstream ss;
template<typename T>
Trace& operator << (T const & data)
{
ss << data;
return *this;
}
~Trace()
{
std::cout << ss.str() << std::endl;
}
};
Now you can use it as:
Trace() << "Hello World\n" << 100 << "\nBye\n";
Output:
Hello World
100
Bye
Live Demo
来源:https://stackoverflow.com/questions/14381311/ostrstream-interprets-constant-string-as-pointer