Reputation: 3417
I implemented a very simple (error) 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, say I have this Main class, that contains all the other objects that my program may contain, and also this Log class. Obviously I would want these "other objects" in my Main class to be able to use the Log object in Main, so the easy solution would be to pass pointers to every class' constructor so that it could 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'm asking if there are different approaches for what I want to accomplish, which is error logging.
Upvotes: 3
Views: 9656
Reputation: 148870
Another common pattern inherited from C would be a global object. Globals are generally frowned upon because they are the symptom of bad design. But it makes sense for a logger to be a global.
That means that the header for your class should contain :
extern Log log;
and the cpp
#include <log.h>
...
Log log; // unique definition for the global Log
Then in any module using the log :
#include <log.h>
...
log.add(...);
The caveat here that there will be static initialization of Log log
. And C++ is not always very nice with order of static initialization : ... Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of
a variable defined in a different translation unit.. The only foolproof way is to ensure that :
std::string
but hopefully not const char *
If in doubt, use the singleton pattern suggested by πάντα ῥεῖ
Upvotes: 1
Reputation: 206567
Another approach to address the requirement...
Use functions in a suitable namespace
instead of a 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's 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 a namespace
vs using a singleton
To support use of functions in a namespace requires bit more code.
However, it simplifies calling code.
Calling code can use
Log::add(...);
instead of
Log::instance()->add(...);
Upvotes: 2
Reputation: 1
That's 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 just accessing
Log::instance().add("Blah Blah",true);
Upvotes: 5