ostrstream interprets constant string as pointer

2019-07-02 21:12发布

问题:

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



标签: c++ strstream