CONTEXT
I have a library used by several software. Some are basic command line and some have a Qt UI.
I want to implement a unique log class inside this library, so every software can use it. However, if we are in a Qt environment, I'd like to use the qDebug()
function with a specific QtMessageHandler
. The goal is to be able to log errors inside this library, and the logs will be printed differently, depending on if the library is used in a UI environment or not (the library has no dependencies to Qt).
WHAT I WANT
Globally, I want something like this:
class Logger
{
public:
template <class T>
void log(const T& tolog) { handler.log(tolog); }
void setHandler(HANDLER???& h) { handler = h; }
const HANDLER???& getHandler() const { return handler; }
private:
HANDLER??? handler;
}
With the handler that will be, for command line software, something very simple like:
class CLHandler
{
public:
template <class T>
void log(const T& tolog) { out << tolog << std::endl; }
private:
std::ostream out;
}
and for UI, I want to use qDebug()
so I can setup a custom QtMessageHandler
to print the error in the UI, and log it in a file:
class UIHandler
{
public:
template <class T>
void log(const T& tolog) { qDebug() << tolog; }
}
PROBLEM
As you can see, the problem is in the class Logger
: what type will be the handler?
I can't really create an interface because of virtual template functions:
class IHandler
{
public:
virtual ~IHandler() = default;
template <class T>
virtual void log(const T& tolog) = 0; // ERROR templated virtual function!
}
Need help
I want the IHandler::tolog
function to be templated because I want to use the power of the operator<<
for both ostream
and qDebug()
. And I don't want to reimplement all the overloads myself (long list for ostream , even longer for qDebug !).
I want to achieve it, no matter how (lambda functions with auto
?)... Any suggestions welcome (I may do something wrong here?).
Thanks :)
It's obviously not possible to have templated virtual functions, but you could use type erasure to "erase" the concrete type, so you don't need a template anymore.
Basically, you create an interface ILoggableValue
that knows how to log your values both using QDebug
and std::ostream
streams, and use templates to generate concrete implementations for different types:
class ILoggableValue {
public:
virtual ~ILoggableValue() = default;
virtual void log(QDebug &os) const = 0;
virtual void log(std::ostream &os) const = 0;
};
template <typename T>
class LoggableValue : public ILoggableValue {
public:
LoggableValue(const T &value) : value{value} {}
void log(QDebug &os) const override {
// implementation of log for QDebug goes here
os << value;
}
void log (std::ostream &os) const override {
// implementation of log for std::ostream goes here
os << value << std::endl;
}
private:
const T &value;
};
Then, you create IHandler
the same way you suggested it, but now you can use ILoggableValue
to erase the template:
class IHandler {
public:
virtual ~IHandler() = default;
virtual void log(const ILoggableValue &tolog) const = 0;
};
class CLHandler : public IHandler {
public:
explicit CLHandler(std::ostream &out) : out{out} {}
void log(const ILoggableValue &tolog) const override {
tolog.log(out);
}
private:
std::ostream &out;
};
class UIHandler : public IHandler {
public:
void log(const ILoggableValue &tolog) const override {
tolog.log(qDebug());
}
};
Finally, you use IHandler
in your Logger
:
class Logger {
public:
Logger(std::unique_ptr<IHandler> h) : handler(std::move(h)) {}
template <class T>
void log(const T& tolog) { handler->log(LoggableValue<T>(tolog)); }
void setHandler(std::unique_ptr<IHandler> &h) { handler = std::move(h); }
const IHandler &getHandler() const { return *handler; }
private:
std::unique_ptr<IHandler> handler;
};
Here is a live example.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.