Registration error in C ++
I have implemented a very simple (buggy) logger class. It looks like this:
#pragma once
#include <string>
extern const char* LOG_DEFAULT_TEXT = "<LOG>\n\n";
class Log
{
public:
Log() : _logs(0), _log(LOG_DEFAULT_TEXT) {};
void add(const char *str, bool nl=true);
void clear();
const char* get() const { return _log.c_str(); }
int getNumLogs() const { return _logs; }
private:
std::string _log;
int _logs;
};
Now my question is, let's say I have this Main class that contains all the other objects that my program can contain as well as this log class. Obviously, I would like these "other objects" in my main class to be able to use the log object in Main, so a simple solution would be to pass pointers to each class constructor so that it can use the log object.
I'm talking about something like this:
//Main.h
...
#include "Log.h"
class Main() {
public:
...
private:
Log _log;
ImportantObject1(&_log); //pass a pointer to _log
ImportantObject2(&_log); //..same
};
This solution seems too clumsy, so I ask if there are different approaches to what I want to accomplish, this is the error log.
source to share
In one of the rare cases, a singleton makes sense:
class Log
{
public:
void add(const char *str, bool nl=true);
void clear();
const char* get() const { return _log.c_str(); }
int getNumLogs() const { return _logs; }
static Log& instance() {
static Log theInstance;
return theInstance;
}
private:
Log() : _logs(0), _log(LOG_DEFAULT_TEXT) {};
std::string _log;
int _logs;
};
This way you can use it elsewhere simply by referring to
Log::instance().add("Blah Blah",true);
source to share
Another approach to meet the requirement ...
Use functions as appropriate namespace
instead of singleton.
Log.h:
namespace Log
{
void add(const char *str, bool nl=true);
void clear();
const char* get();
int getNumLogs();
}
Log.cpp:
namespace detail
{
// This is almost same as the class from OP post.
struct LogImpl
{
LogImpl(std::string const& log) : _logs(0), _log(log) {};
void add(const char *str, bool nl=true);
void clear();
const char* get() const { return _log.c_str(); }
int getNumLogs() const { return _logs; }
std::string _log;
int _logs;
};
}
namespace Log
{
// This mimics the singleton implementation...
// Create only one instance of LogImpl.
static detail::LogImpl impl("<LOG>\n\n");
void add(const char *str, bool nl)
{
impl.add(str, nl);
}
void clear()
{
impl.clear();
}
const char* get()
{
return impl.get();
}
int getNumLogs()
{
return impl.getNumLogs();
}
}
namespace Log
{
void add(const char *str, bool nl=true);
void clear();
const char* get();
int getNumLogs();
}
Using functions in namespace
vs using singleton
It takes a bit more code to support the use of functions in the namespace.
However, it simplifies the calling code.
The calling code can use
Log::add(...);
instead
Log::instance()->add(...);
source to share
Another common pattern inherited from C would be the global object. Globals tend to be frowned upon because they are a symptom of poor design. But the logical log makes sense to be global.
This means that the header for your class should contain:
extern Log log;
and cpp
#include <log.h>
...
Log log; // unique definition for the global Log
Then, in any module using the log:
#include <log.h>
...
log.add(...);
Here's a caveat that there will be static initialization Log log
. And C ++ is not always very good with static initialization order: ... Otherwise, variable initialization is undefined sequenced relative to initialization of a variable defined in a different translation unit. The only sure way is to ensure that:
- The log construct is independent of any other statically initialized variable needing a constructor phase in another translation unit - it includes
std::string
, but hopefully notconst char *
- the global variable is used once before starting multithreading
When in doubt, use the singleton pattern suggested by πάντα ῥεῖ
source to share