My current implementation, simplified:
#include <string> #include <memory> class Log { public: ~Log() { // closing file-descriptors, etc... } static void LogMsg( const std::string& msg ) { static std::unique_ptr<Log> g_singleton; if ( !g_singleton.get() ) g_singleton.reset( new Log ); g_singleton->logMsg( msg ); } private: Log() { } void logMsg( const std::string& msg ) { // do work } };
In general, I am satisfied with this implementation because:
However, the negatives are:
So here are my questions directed towards those developers who are successful in exorcising all singletons from their C++ code:
I want to avoid passing a Log instance all over my code, if at all possible - note: I am asking because, I, too, want to exorcise all Singletons from my code if there is a good, reasonable alternative.
Why a singleton? A singleton is a creational design pattern whose purpose is to ensure that only one instance of a class exists. We want our logger to be a singleton as we only want one logger instance to be running and logging information at any one time.
A logger is, perhaps, the most iconic example of a singleton use case. You need to manage access to a resource (file), you only want one to exist, and you'll need to use it in a lot of places.
First: the use of std::unique_ptr
is unnecessary:
void Log::LogMsg(std::string const& s) { static Log L; L.log(s); }
Produces exactly the same lazy initialization and cleanup semantics without introducing all the syntax noise (and redundant test).
Now that is out of the way...
Your class is extremely simple. You might want to build a slightly more complicated version, typical requirements for log messages are:
on top of the message itself.
As such, it is perfectly conceivable to have several objects with different parameters:
// LogSink is a backend consuming preformatted messages // there can be several different instances depending on where // to send the data class Logger { public: Logger(Level l, LogSink& ls); void operator()(std::string const& message, char const* function, char const* file, int line); private: Level _level; LogSink& _sink; };
And you usually wrap the access inside a macro for convenience:
#define LOG(Logger_, Message_) \ Logger_( \ static_cast<std::ostringstream&>( \ std::ostringstream().flush() << Message_ \ ).str(), \ __FUNCTION__, \ __FILE__, \ __LINE__ \ );
Now, we can create a simple verbose logger:
Logger& Debug() { static Logger logger(Level::Debug, Console); return logger; } #ifdef NDEBUG # define LOG_DEBUG(_) do {} while(0) #else # define LOG_DEBUG(Message_) LOG(Debug(), Message_) #endif
And use it conveniently:
int foo(int a, int b) { int result = a + b; LOG_DEBUG("a = " << a << ", b = " << b << " --> result = " << result) return result; }
The purpose of this rant ? Not all that is a global need be unique. The uniqueness of Singletons is generally useless.
Note: if the bit of magic involving std::ostringstream
scares you, this is normal, see this question
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With