views:

505

answers:

2

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.

+5  A: 

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.

John Millikin
You don't need the do/while around the block. You can have free standing braces just to control the scope of expressions.
KayEss
My primary aim is optimization. as much as I would like to keep a single LOG() macro that works for many different types passed I will need to probably create an exception for this one to distinguish it. the problem is that there will indeed still remain some logs in release mode (depending on the logging severity) and for these I would not want the overhead described above, thus I may need to create the exception #define for each severity.I've tried to keep the format of the logging syntax unchanged as much as possible on this legacy project.
Marius
I'm marking it as useful, but will not mark it as the solution just yet.
Marius
@KayEss: The do/while is so the semicolon can be added to the end of the line, which makes the call to `LOG()` look more natural. See http://stackoverflow.com/questions/154136/
John Millikin
@Marius: While I completely understand the goal behind using a unified logging macro, in practice, it's very difficult to implement properly. Using separate `LOG_DEBUG()`, `LOG_WARNING()`, `LOG_ERROR()`, etc macros is much more manageable.
John Millikin
@Millikin: You could try doing something using boost preprocessor. /**/#define LOG_WARN(...) printf( __VA_ARGS__ )/*LINE BREAK*/#define LOG(LV, ...)/*LINE BREAK*/BOOST_PP_CAT( LOG_ , LV )( __VA_ARGS__ )/*LINE BREAK*/LOG(WARN,"TESTING"); /*cpp expands this to "printf( "TESTING" );" */
KitsuneYMG
of course the VA_ARGS is supposed to have a double underscore leading and trailing it.
KitsuneYMG
@Millikin: Thanks for explaining the do-while... The brackts serves a dual purpose, firstly creating scope for the 's' variable as well as grouping the statements when part of, say, an un-bracketed if/else statement. But is it truly necessary? One can surely simply have { ... }; in the middle of code without a problem? Also I do have separate LOG_DEBUG() LOG_INFO() calls, what I meant by unified is that each one of those severities can handle multiple types of INFO, DEBUG, etc. message parameter list types.
Marius
@Marius: See the link I posted. Just using braces might be invalid syntax, depending on where the macro is used. I don't understand the second part of your comment -- could you elaborate on that in the question?
John Millikin
@Millikin: I only mean that I agree with you on that separate LOG defines make it more manageable. I can remove certain severity logging completely in different builds etc. Which is what I did. By unify I mean that a single LOG_XXXX macro will expand and actually match one of several overloaded methods (as well as a varargs one). So by unify I mean that the one define for, say, DEBUG will service several different types of parameter lists.
Marius
+2  A: 

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;
}
Éric Malenfant
Thanks for the info! This led me to my preferred solution... I knew I was on the right track. The best way for me to get it working consistently is thus as follows: `log(stringstream().flush() << "hello " << 1);`
Marius
PS: What does BYT mean? And how are you defining the `log()` function differently from me?
Marius
Oups! It's a typo. I wanted to write "By The Way". Corrected
Éric Malenfant
lol, BTY is still not BTW....
Marius
Aaargh! In retrospect, it would have been shorter to write it in full instead :)
Éric Malenfant