I am trying to implement a logging system abstracted behind a locator service (in the style of this guide) in such a way that the logging system can be subclassed for various different logging situations. I would prefer to be able to use printf style format strings instead of <<, but that means supporting variable number of arguments. Variadic templates can easily solve this, however, they cannot be virtual, which means that the base logging class cannot be abstract (as an interface).
Ideally what I'd need is some way to have the parent method not templated but just accepting of a parameter pack which it would forward to the right (templated) child method. I've mostly seen two ways of doing this, one which is using va_list which apparently is unsafe, complicated and not really meant to interact with variadic templates easily, and CRTP, which would work but means that no pointer can be declared of the abstract base class in the locator object without knowing the subclass type, which defeats the purpose.
Here is example code assuming that virtual templates were a thing:
class Logger
{
public:
template <typename ... Args>
virtual void print(char *filename, int line, std::string &&msg, Args&& ... args) = 0;
protected:
template <typename ... Args>
std::string format(char *filename, int line, std::string &msg, Args&& ... args)
{
std::string output = "%s at line %i: " + msg;
int size = std::snprintf(nullptr, 0, output.c_str());
// +1 for null termination
std::vector<char> buf(size + 1);
std::snprintf(&buf[0], buf.size(), output.c_str(), filename, line, args...);
return std::string(&buf[0]);
}
};
class PrintLogger : public Logger
{
public:
template <typename ... Args>
void print(char *filename, int line, std::string &&msg, Args&& ... args)
{
std::cout << format(filename, line, msg, args...);
}
};
Here is example code with the CRTP solution (which breaks the locator):
template <typename Loggertype>
class Logger
{
public:
template <typename ... Args>
void print(char *filename, int line, std::string &&msg, Args&& ... args)
{
Loggertype::print(filename, line, msg, args...);
}
protected:
template <typename ... Args>
std::string format(char *filename, int line, std::string &msg, Args&& ... args)
{
std::string output = "%s at line %i: " + msg;
int size = std::snprintf(nullptr, 0, output.c_str());
// +1 for null termination
std::vector<char> buf(size + 1);
std::snprintf(&buf[0], buf.size(), output.c_str(), filename, line, args...);
return std::string(&buf[0]);
}
};
class PrintLogger : public Logger<PrintLogger>
{
public:
template <typename ... Args>
void print(char *filename, int line, std::string &&msg, Args&& ... args)
{
std::cout << format(filename, line, msg, args...);
}
};
And here is the locator:
class Global {
public:
static void initialize() { logger = nullptr; }
static Logger& logging()
{
if (logger == nullptr)
{
throw new std::runtime_error("Attempt to log something before a logging instance was created!");
}
return *logger;
}
static void provide_logging(Logger *new_logger) { logger = new_logger; }
private:
static Logger *logger;
};
Aucun commentaire:
Enregistrer un commentaire