首页 > 解决方案 > 多态记录器(虚拟模板函数?)

问题描述

语境

我有几个软件使用的库。有些是基本的命令行,有些有 Qt UI。

我想在这个库中实现一个独特的日志类,这样每个软件都可以使用它。但是,如果我们在 Qt 环境中,我想将该qDebug()函数与特定的QtMessageHandler. 目标是能够在这个库中记录错误,并且日志的打印方式会有所不同,具体取决于该库是否在 UI 环境中使用(该库与 Qt 没有依赖关系)。


我想要的是

在全球范围内,我想要这样的东西:

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;
}

对于命令行软件,处理程序将非常简单,例如:

class CLHandler
{
public:
    template <class T>
    void log(const T& tolog) { out << tolog << std::endl; }

private:
    std::ostream out;
}

对于 UI,我想使用qDebug()这样我可以设置一个自定义QtMessageHandler来在 UI 中打印错误,并将其记录在一个文件中:

class UIHandler
{
public:
    template <class T>
    void log(const T& tolog) { qDebug() << tolog; }
}

问题

如您所见,问题出在类中Logger:处理程序将是什么类型?

由于虚拟模板功能,我无法真正创建界面:

class IHandler
{
public:
    virtual ~IHandler() = default;

    template <class T>
    virtual void log(const T& tolog) = 0; // ERROR templated virtual function!
}

需要帮忙

我希望对IHandler::tolog函数进行模板化,因为我想同时使用 和 的operator<<强大ostream功能qDebug()。而且我不想自己重新实现所有重载(ostream的长列表, qDebug更长的列表!)。

我想实现它,无论如何(lambda 函数与auto?)...欢迎任何建议(我可能在这里做错了什么?)。

谢谢 :)

标签: c++qtc++14

解决方案


显然不可能有模板化的虚函数,但您可以使用类型擦除来“擦除”具体类型,因此您不再需要模板。

基本上,您创建一个接口,该接口ILoggableValue知道如何使用QDebugstd::ostream流记录您的值,并使用模板为不同类型生成具体实现:

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;
};

然后,您IHandler按照建议的方式创建它,但现在您可以使用它ILoggableValue来擦除模板:

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());
    }
};

最后,您IHandler在您的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;
};

是一个活生生的例子。


推荐阅读