我正在尝试从大型代码库中模块化日志代码,因为实时日志框架(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
类?代码库根本没有模板化,我非常不愿意走这条路,因为这将是一项繁琐的工作。
如果可以通过将模板保持在功能级范围来完成,我会很高兴看到解决方案。函数指针也是可以接受的,但我不确定获取可变模板函数地址的可行性。
由于
答案 0 :(得分:0)
我退了一步,剥掉了一些模板。原始函数指针给了我我需要的东西。我还希望如果我将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);
}
答案 1 :(得分:0)
正如其他人已经说过的那样,模板需要实例化,换句话说,编译器必须知道模板函数可能与一组特定的参数一起使用,因此创建一个相应的函数实例。
如果你想“伪造”模板函数的虚函数的行为,函数A<T>
发送到B1<T>
,B2<T>
,B3<T>
之一...动态,那么你需要确保实例化所有B
函数。为了做到这一点,A
的实现需要“了解”所有不同的B
。然后,您可以应用Visitor Pattern。
第二个选项是让编译器知道哪个B
将用于A<T>
的给定实例化。在其他福特中,您使用Context
模板ConcreteLoggerType
。
第三个选项是不对B
进行模板化,而是以某种方式动态处理不同的T
。 James使用变量参数列表描述了如何执行此操作。
总之,您的选择是:
Context
Logger
Logger
作为C ++模板编程能力的忠实粉丝,我倾向于第二个解决方案Policy-Based Design。