3

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";
}
l4mpi
  • 5,103
  • 3
  • 34
  • 54

2 Answers2

5

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

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Thanks for the explanation... but, how do I fix this? Is there a better way than prepending `<< left` to all debug statements? – l4mpi Jan 17 '13 at 14:46
  • Thank you, this would have been the best way to implement it... Not sure if we'll use this though as the actual class is somewhat bigger and more complicated and our policy is to make as few changes as possible to the code - this application is almost 15 years old (ostrstream wasn't yet deprecated back then ^^) and most developers who worked on it have left the company a long time ago, so if we break something we're screwed :/ – l4mpi Jan 17 '13 at 15:04
  • @l4mpi: The single `operator<<` can print everything as long as it is printable by `std::ostream` because it is template. So it means it will not break anything. If you need extra functionality then you can add to it anytime, with little effort. – Nawaz Jan 17 '13 at 15:06
5

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.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Thanks for the explanation - see my comment to nawaz' answer, what would be the best workaround for this? – l4mpi Jan 17 '13 at 14:46
  • No, the standard way is to do something like `Tracer() << flush` to get an lvalue reference to the stream. And if you have one of the newest compilers, with rvalue support, it should work anyway. This is an issue that was fixed in the C++11 revision. – Bo Persson Jan 17 '13 at 14:53
  • Okay, thanks. The compiler in question is g++ 4.1.2 RedHat, which sadly isn't c++11 compliant... – l4mpi Jan 17 '13 at 14:57
  • @l4mpi: Don't use `std::ostrstream`. It is **deprecated**. Also don't derived from stream classes. Composition is better in this case. Write a wrapper class, as shown in my answer. – Nawaz Jan 17 '13 at 15:00