I spend some time with redesigning a logger class I did once into a policy based approach after reading an article about policy based design and wanting to try something myself.
Some code:
template <class Filter, class Formatter, class Outputter>
class LoggerImpl : public LoggerBase {
public:
LoggerImpl(const Filter& filter = Filter(), const Formatter& formatter = Formatter(), const Outputter& outputter = Outputter());
~LoggerImpl();
void log(int channel, int loglevel, const char* msg, va_list list) const;
private:
const Filter mFilter;
const Formatter mFormatter;
const Outputter mOutputter;
};
template <class Filter, class Formatter, class Outputter>
LoggerImpl<Filter, Formatter, Outputter>::LoggerImpl(const Filter& filter, const Formatter& formatter, const Outputter& outputter) :
mFilter(filter), mFormatter(formatter), mOutputter(outputter) {
debuglib::logdispatch::LoggerMgr.addLogger(this);
}
typedef LoggerImpl<NoFilter, SimpleFormatter, ConsoleOutputter> ConsoleLogger;
typedef LoggerImpl<ChannelFilter, SimpleFormatter, VSOutputter> SimpleChannelVSLogger;
typedef LoggerImpl<NoFilter, SimpleFormatter, FileOutputter> FileLogger;
ConsoleLogger c;
SimpleChannelVSLogger a(const ChannelFilter(1));
FileLogger f(NoFilter(), SimpleFormatter(), FileOutputter("log.txt"));
// macro for sending log message to all current created loggers
LOG(1, WARN, "Test %d", 1423);
Depending on the logger I need to pass additional information like the logchannel within the SimpleChannelVsLogger or the filename of the logfile in the FileOututter.
I'm passing the parameters to the constructor of LoggerImpl as const reference and copy them into the object stored in the logger class afterwards. There is a need to copy them because the lifetime extension is not transitive through a function argument that occurs when binding the temporary created object to a const reference (more on this here: Does a const reference prolong the life of a temporary?).
So first thing here: If I don't want to use pointers as I am not interested in runtime allocation when using templates, I guess there is no other solution than copying the temporary created objects in the way like above?
The actual problem in the copying stuff now comes with the FileOutputter: An ofstream cannot be copied of course, so how can I copy the FileOutputter object containing the stream? I came up with following solution to overcome this problem:
struct FileOutputter {
// c_tor
FileOutputter() {}
// c_tor
explicit FileOutputter(const char* fname) {
mStream = std::make_shared<std::fstream>(fname, std::fstream::out);
}
// The copy c_tor will be invoked while creating any logger with FileOutputter
// FileLogger f(NoFilter(), SimpleFormatter(), FileOutputter("log.txt"));
// as all incoming paramters from the constructors stack frame are copied into the current instance of the logger
// as copying a file-stream is not permitted and not good under any means
// a shared pointer is used in the copy c_tor
// to keep the original stream until no reference is referring to it anymore
FileOutputter(const FileOutputter& other) {
mStream = other.mStream;
}
~FileOutputter() {
}
void out(const char* msg) const {
*mStream << msg;
}
std::shared_ptr<std::fstream> mStream;
};
Somehow I have to feeling that this seems a bit complex for a "simple logger class", however this may be just a "problem" with the policy based design approach in this case.
Any thoughts are welcome
= Filter()
” etc.) in the function definition – Huxley