C++ 创建一个具有可变模板方法的接口

C++ creating an interface that has variadic template methods

所以基本上我正在寻找一种方法来为不同平台创建具有各种实现的接口。通常这会很简单,但我想为记录器创建一个接口,并且能够具有如下功能是我的目标:

class Log {
public:
    template<typename ...Args>
    virtual void log(const char* fmt, const Args&... args) const = 0;

    template<typename ...Args>
    virtual void warn(const char* fmt, const Args&... args) const = 0;

    template<typename ...Args>
    virtual void error(const char* fmt, const Args&... args) const = 0;
};

现在显然这行不通,因为您不能拥有纯虚拟模板函数。

我看到的一种方法是使日志 class 成为 CRTP(奇怪地重复出现的模板模式)class 并使用类似于以下内容的方法来获得所需的行为:

//Log.h (template<typename T> class Log {...})
template<typename ...Args>
void log(const char* fmt, const Args&... args) const {
    //the following line would essentially downcast the 'this' pointer and
    //call it's version of log instead...
    reinterpret_cast<T*>(this)->log(fmt, args...);
}

//ExampleLog.h (class ExampleLog : public Log<ExampleLog> {...})
template<typename ...Args>
void log(const char* fmt, const Args&... args) const {
    m_logger->log(fmt, args...);
}

缺点是这会使界面难以使用,经常需要使用 Log<Impl>,其中 Impl(实现)可能不是 known/exposed。

我真的不知道如何创建一个包含具有可变数量和类型参数的函数的接口...?

无论您遇到错误、警告还是其他情况,都归结为一个记录的文本字符串。您的虚函数只需要将单个 std::string 作为参数,而 Log 基础 class 中的所有这些函数将负责将参数格式化为单个 std::string 然后调用真正的虚函数:

class Log {
public:
    template<typename ...Args>
    void log(const char* fmt, Args && ...args) const
    {
       do_log(do_format(std::forward<Args>(args)...));
    }

    template<typename ...Args>
    void warn(const char* fmt, Args && ...args) const
    {
       do_warn(do_format(std::forward<Args>(args)...));
    }

    template<typename ...Args>
    void error(const char* fmt, Args && ...args) const
    {
       do_error(do_format(std::forward<Args>(args)...));
    }

private:

    template<typename ...Args>
    inline std::string do_format(const char *fmt, Args && ...args)
    {
         // Your homework assignment goes here
    }

    virtual void do_log(const std::string &) const = 0;

    virtual void do_warn(const std::string &) const = 0;

    virtual void do_error(const std::string &) const = 0;
};

所以现在剩下要做的就是实施 do_format()。这是您无论如何都必须做的事情,在您的日志记录 class 中,但它需要在这里完成,有效地 type-erasing 所有模板参数并用单个 std::string 替换它们。如果您的日志记录功能归结为记录 std::string 以外的其他内容,那么在这里构造它,这就是被扔进您的虚拟功能的内容。

至于为什么你的可变参数模板参数应该使用 && 而不是 const &,这个主题在其他地方被相当广泛地涵盖了,那里的主题被称为 "perfect forwarding",所以我建议您参考那些众多的 Whosebug 和 Google 搜索结果以获取更多详细信息。

此外,正如其他地方广泛介绍的那样,您必须 declare and define do_format() inline。这将导致明显的代码膨胀。有多种减少代码膨胀的技术,但这又是另一个讨论主题。

So basically I'm looking for a way to create an interface with various implementations for different platforms.

因此您可能会创建相同的 class 但使用不同的文件实现不同,而您根据 platform/OS/... 或通过预定义的宏仅使用正确的文件。

类似

log_windows.h

template<typename ...Args>
void log(const char* fmt, const Args&... args)
{
// Windows implementation
}

template<typename ...Args>
void warn(const char* fmt, const Args&... args)
{
// Windows implementation
}

template<typename ...Args>
void error(const char* fmt, const Args&... args)
{
// Windows implementation
}

log_nix.h

template<typename ...Args>
void log(const char* fmt, const Args&... args)
{
// *nix implementation
}

template<typename ...Args>
void warn(const char* fmt, const Args&... args)
{
// *nix implementation
}

template<typename ...Args>
void error(const char* fmt, const Args&... args)
{
// *nix implementation
}

log.h

#ifdef (_WIN32)
#include "log_windows.h"
#else
#include "log_nix.h"
#endif