Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

stringstream temporary ostream return problem

I'm creating a logger with the following sections:

// #define LOG(x) // for release mode
#define LOG(x) log(x)

log(const string& str);
log(const ostream& str);

With the idea to do:

LOG("Test");
LOG(string("Testing") + " 123");
stringstream s;
LOG(s << "Testing" << 1 << "two" << 3);

This all works as intended, but when I do:

LOG(stringstream() << "Testing" << 1 << "two" << 3);

It does not work:

void log(const ostream& os)
{
  std::streambuf* buf = os.rdbuf();
  if( buf && typeid(*buf) == typeid(std::stringbuf) )
  {
    const std::string& format = dynamic_cast<std::stringbuf&>(*buf).str();
    cout << format << endl;
  }
}

results in 'format' containing junk data instead of the usual correct string.

I think this is because the temporary ostream returned by the << operator outlives the stringstream it comes from.

Or am I wrong?

(Why does string() work in this way? Is it because it returns a reference to itself? I'm assuming yes.)

I would really like to do it this way as I would be eliminating an additional allocation when logging in release mode.

Any pointers or tricks to get it done this way would be welcomed. In my actual solution I have many different log functions and they are all more complex than this. So I would prefer to have this implemented somehow in the calling code. (And not by modifying my #define if possible)

Just to give an idea, an example of one of my actual #defines:

#define LOG_DEBUG_MSG(format, ...) \
  LogMessage(DEBUG_TYPE, const char* filepos, sizeof( __QUOTE__( @__VA_ARGS__ )), \
  format, __VA_ARGS__)

which matches varargs printf-like log functions taking char*, string() and ostream() as well as non-vararg functions taking string(), exception() and HRESULT.

like image 551
Marius Avatar asked Oct 08 '09 22:10

Marius


2 Answers

I think I see what's happening. This produces the expected output:

log(std::stringstream() << 1 << "hello");

while this does not:

log(std::stringstream() << "hello" << 1);

(it writes a hex number, followed by the "1" digit)

A few elements for the explanation:

  • An rvalue cannot be bound to a non-const reference
  • It is OK to invoke member functions on a temporary
  • std::ostream has a member operator<<(void*)
  • std::ostream has a member operator<<(int)
  • For char* the operator is not a member, it is operator<<(std::ostream&, const char*)

In the code above, std::stringstream() creates a temporary (an rvalue). Its lifetime is not problematic, as it must last for the whole full expression it is declared into (i.e, until the call to log() returns).

In the first example, everything works ok because the member operator<<(int) is first called, and then the reference returned can be passed to operator<<(ostream&, const char*)

In the second example, operator<<(cannot be called with "std::stringstream()" as a 1st argument, as this would require it to be bound to a non-const reference. However, the member operator<<(void*) is ok, as it is a member.

By the way: Why not define the log() function as:

void log(const std::ostream& os)
{
    std::cout << os.rdbuf() << std::endl;
}
like image 90
Éric Malenfant Avatar answered Nov 14 '22 00:11

Éric Malenfant


Alter your LOG() macro to this:

#define LOG(x) do { std::stringstream s; s << x; log(s.str()); } while(0)

That will let you use the following syntax in your debugging logs, so you don't have to manually construct the string stream.

LOG("Testing" << 1 << "two" << 3);

Then define it to nothing for release, and you'll have no extra allocations.

like image 6
John Millikin Avatar answered Nov 13 '22 23:11

John Millikin