【问题标题】:Faking virtual variadic template function伪造虚拟可变参数模板函数
【发布时间】:2013-09-07 18:41:35
【问题描述】:

我正在尝试将大型代码库中的日志代码模块化,因为实时日志框架 (Apache) 使我们的代码与其紧密耦合,这使得编写单元测试变得非常困难。我被我无法拥有虚拟模板化功能的事实所困扰。我目前的做法可以总结如下:

// Context.h
struct Logger
{
    template <typename... Args>
    void operator()(const char* aFormat, Args&&... aArgs)
    {
         // This function would ideally be virtual.
         // Is there a funky way to get this function to call
         // a derived class' implementation instead.
         std::cerr << "I don't want to see this printed" << std::endl;
    }
};

class Context
{
public:
    Context(const Logger& aLogger)
    :   iLogger(aLogger)
    {
    }

    template <typename... Args>
    void DEBUG(const char* aFormat, Args&&... aArgs)
    {
        iLogger(aFormat, aArgs...);
    }

private:
    const Logger& iLogger;
};


// MyType.h
#include "Context.h"

class MyType
{
public:
    MyType(Context& aCtx)
    :   iCtx(aCtx)
    {
        DEBUG("> ctor");
        DEBUG("< ctor. this=%p", this);
    }

private:
    template <typename... Args>
    void DEBUG(const char* aFormat, Args&&... aArgs)
    {
        iCtx.DEBUG(aFormat, aArgs...);
    }

    Context& iCtx;
};


// main.cpp
#include "MyType.h"

template <typename... Args>
static void StdErrLog(const char* aFormat, Args&&... aArgs)
{
    fprintf(stderr, aFormat, aArgs...);
}

struct StdErrLogger : public Logger
{

    // This function never gets called because it's not virtual.
    template <typename... Args>
    void operator(const char* aFormat, Args&&... aArgs)
    {
        StdErrLog(aFormat, aArgs...);
    }
}

int main(...)
{
    StdErrLogger logger; // For unit tests this could be 'EmptyLogger' for example.
    Context ctx(logger);

    MyType t(ctx);
}

这么近,这么远。 在没有模板化 Context 类的情况下,我能做些什么来完成这项工作?代码库根本没有模板化,我非常不愿意走这条路,因为这将是很多乏味的工作。

如果可以通过将模板保持在功能级别范围内来完成,我会很高兴看到解决方案。函数指针也是可以接受的,但我不确定获取可变参数模板函数地址的可行性。

谢谢

【问题讨论】:

  • 您需要实现中的格式字符串和参数,还是只需要格式化的结果?如果您需要的不仅仅是格式化的结果,是否有一种中间形式可以做到?您的记录器或格式化程序的实现有何不同?
  • 问题在于模板需要由编译器实例化,即编译器必须从函数模板创建实际函数。因此,功能模板的实现需要在每个翻译单元中可用(如果要实例化它们)。函数模板不能是虚拟的,因为使用基类的函数需要实例化(所有)派生类的覆盖器,而这对于您想要/可以使用虚拟函数实现的单独编译是不可能的。
  • @Yakk 为了测试,我打算使用一个什么都不打印的记录器(或者如果我需要它可能是 stderr)。对于生产,我会调用 Apache 运行时的 ap_log_error。我想我需要实现中的参数。
  • @cooky451 CRTP 意味着我必须在类范围内对 Context 进行模板化。这将导致其他地方很多其他变化
  • 为什么?为什么不是预先格式化的字符串?然后将"%s", string 发送到实际的后端记录器。通过将格式化程序与记录器分离,您的问题就会消失。

标签: c++ templates variadic-templates


【解决方案1】:

我退后一步,剥掉了几层模板。原始函数指针给了我我需要的东西。如果我将nullptr 作为函数传递,我还希望编译器足够聪明,不会生成任何代码。缺点是每个 LogFunction 都必须做va_list/start/end faff。

// Context.h
typedef void (*LogFunction)(const char*, ...);

class Context
{
public:
    Context(LogFunction aDebugFunc)
    :   iDebugFunc(aDebugFunc)
    {
    }

    template <typename... Args>
    void DEBUG(const char* aFormat, Args&&... aArgs)
    {
        if (iDebugFunc)
        {
            iDebugFunc(aFormat, aArgs...);
        }
    }

private:
    LogFunction iDebugFunc;
};


// main.cpp
#include <cstdarg>

void StdErrLogger(const char* aFormat, ...)
{
    va_list args;
    va_start(args, aFormat);

    fprintf(stderr, aFormat, args);

    va_end(args);
}

int main(...)
{
    Context ctx(StdErrLogger);
    MyType t(ctx);
}

【讨论】:

    【解决方案2】:

    正如其他人已经说过的,模板需要被实例化,换句话说,编译器必须知道模板函数可能与一组特定的参数一起使用,从而创建函数的相应实例。

    如果你想动态地“伪造”模板函数的虚函数行为,函数 A&lt;T&gt; 发送到 B1&lt;T&gt;B2&lt;T&gt;B3&lt;T&gt;... 之一,那么你需要确保所有的B 函数被实例化。为了做到这一点,A 的实现需要“了解”所有不同的可能Bs。然后,您可以应用 Visitor Pattern 之类的内容。

    第二个选项是让编译器知道哪个B 将用于A&lt;T&gt; 的给定实例化。在其他福特中,您可以使用 ConcreteLoggerType 模板 Context

    第三个选项是根本不模板化Bs,而是以某种方式动态处理不同的Ts。 James 描述了如何使用变量参数列表来做到这一点。

    总的来说,您的选择是:

    1. 使用一组固定的记录器。
    2. Logger 模板化Context
    3. 使用非模板Logger

    作为 C++ 模板编程功能的忠实粉丝,我倾向于第二种解决方案 Policy-Based Design

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-18
      • 2015-05-06
      • 2016-09-02
      • 2018-02-03
      • 2016-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多