It's not really a very good idea, since someone will fatally
forget the unlock
at some point, causing all of the threads to
hang at the next log. There's also the issue of what happens if
one of the expressions you're logging throws. (It shouldn't
happen, since you don't want to have actual behavior in the log
statement, and things that don't have any behavior shouldn't
throw. But you never know.)
The usual solution for logging is to use a special temporary
object, which grabs the lock in its constructor, and frees it in
the destructor (and also does a flush, and ensures that there is
a trailing '\n'
). This can be done very elegantly in C++11,
using move semantics (because you generally want to create the
instance of the temporary in a function, but the temporary whose
destructor should act is outside the function); in C++03, you
need to allow copying, and ensure that it is only the final copy
which releases the lock.
Roughly speaking, your Log
class would look something like:
struct LogData
{
std::unique_lock<std::mutex> myLock
std::ostream myStream;
LogData( std::unique_lock<std::mutex>&& lock,
std::streambuf* logStream )
: myLock( std::move( lock ) )
, myStream( logStream )
{
}
~LogData()
{
myStream.flush();
}
};
class Log
{
LogData* myDest;
public:
Log( LogData* dest )
: myDest( dest )
{
}
Log( Log&& other )
: myDest( other.myDest )
{
other.myDest = nullptr;
}
~Log()
{
if ( myDest ) {
delete myDest;
}
}
Log& operator=( Log const& other ) = delete;
template <typename T>
Log& operator<<( T const& obj )
{
if ( myDest != nullptr ) {
myDest->myStream << obj;
}
}
};
(If your compiler doesn't have move semantics, you'll have to
fake it somehow. If worst comes to worst, you can just make the
single pointer member of Log mutable, and put the same code in
a copy constructor with the traditional signature. Ugly, but as
a work-around...)
In this solution, you would have a function log
, which returns
an instance of this class, with either a valid LogData
(allocated dynamically), or a null pointer, depending on whether
logging is active or not. (It's possible to avoid the dynamic
allocation, by using a static instance of a LogData
which has
functions to start a log record, and to end it, but it is
a little bit more complicated.)