9

我有这个日志记录系统,我正在寻找一些字符串操作的快捷方式。

日志系统通过功能宏使用,然后转发到单个函数调用。例如#define Warning(...) LogMessage(eWarning, __VA_ARGS__);

然后 LogMessagesnprintf进入一个新的缓冲区,然后将该消息呈现给碰巧安装的任何日志目标;printf、OutputDebugString 等

不幸的是,我遇到了一个问题,我们拥有的缓冲区不够大,因此输出被截断。我还意识到,如果输出消息中包含百分比符号,此方法将失败,因为 snprintf 将尝试处理 va_args。最后,由于我们的大多数日志消息不使用 va_args,复制字符串只是为了将其呈现给记录器似乎很愚蠢。

所以-给定我的函数原型,我是否应该能够根据省略号的存在来重载?换句话说,我是否应该能够假设我可以做类似的事情:

LogMessage(LogLevel, const char* message, ...);
LogMessage(LogLevel, const char* message);

我的谷歌尝试并没有产生任何特别有用的东西(只是告诉我,如果没有其他方法,椭圆将匹配,与我的要求不同,没有任何匹配),我最初的实现只是给了我一个模棱两可的函数调用错误。

有了这个错误,我应该接受我不能这样做,但我想知道这只是我正在使用的编译器,还是我做错了。我可以达到类似的效果

// edited version of what I really have to remove our local APIs,
// please excuse minor errors
const char* message = NULL;
char buffer[512];

va_list args;
va_start(args, format);

if(strcmp(format, "%s") == 0) {
    message = va_arg(args, const char*);
}
else if (strchr(format, '%') == NULL) {
    message = format;
}
else {
    vsnprintf(buffer, 512, format, args);
    message = buffer;
}

va_end(args);

...但这在典型情况下似乎很浪费,只需通过传递的参数数量即可知道。例如,如果省略号不匹配任何内容,请选择其他函数?如果这不起作用,我可以尝试另一种方法,不需要用户用宏名称来决定将调用哪个函数?老实说,一旦我意识到如果有人Error("Buffer not 100% full");在他们的日志消息中随意说出并得到“缓冲区不是 1007.732873e10ull”,那么“浪费”就不再那么重要了。

编辑:虽然我的例子已经回答了“不要那样做”,但问题本身可以回答吗?

4

4 回答 4

3

我还意识到,如果输出消息中包含百分比符号,此方法将失败,因为 snprintf 将尝试处理 va_args。

然后来电者小心。如果您的函数被记录为采用 printf 样式的格式字符串,那么调用者有责任转义任何百分号。尝试处理无效的格式字符串并不是你的工作。

老实说,一旦我意识到如果有人Error("Buffer not 100% full");在他们的日志消息中随意说出并得到“缓冲区不是 1007.732873e10ull”,那么“浪费”就不再那么重要了。

我认为您最好遵循 C++ 精神。在 Java 方法中,通常检查有效参数并在传递无效值时抛出异常。在 C++ 中,您只是让调用者在脚下开枪。最好让他们编写100%%而不是跳过箍以防止他们学习如何正确调用您的函数。

于 2010-09-02T00:23:47.423 回答
3

我受到这个问题的原始答案的启发,但提出了轻微的改进。

static void LogMessage(LogLevel level, const char* message);

template <typename T>
static void LogMessage(LogLevel level, const char* format, T t, ...)
{
    LogMessageVA(level, format, (va_list)&t);
}

static void LogMessageVA(LogLevel level, const char* format, va_list argptr);

这可以在不必“假设”第二个参数是 const char* 的情况下工作。

于 2014-03-05T22:58:07.663 回答
2

在 C++11 中,您可以对单参数情况使用具有显式特化的可变参数模板:

void bar(int a, ...) {
  // va_list stuff
}

template <typename... T>
void foo(int a, T... args) { // (1)
  bar(a, args...); // or do all the vararg stuff here directly
}

template <>
void foo(int a) {            // (2)
  printf("single\n");
}

然后:

//foo();    // compile error, as expected
foo(1);     // uses (2)
foo(2,1);   // uses (1)
foo(3,1,"asdf"); // uses (1)
...
于 2012-09-12T02:22:26.387 回答
1

好的,我想我想出了一个解决问题的方法。

事实是,您不能仅根据椭圆是否有参数来重载。即,您不能拥有仅根据椭圆的存在而变化的签名的函数。

但是,如果我从椭圆原型中删除参数,可以执行我所要求的操作。const char*IE

LogMessage(LogLevel, ...);
LogMessage(LogLevel, const char* message);

是明确的,但现在你必须假设第一个参数是 a const char*,但它很可能不是。听从 John Kugelman 的建议,也许没问题;您记录了允许的参数并且用户要小心。如果只有 a const char*,则将调用非省略号函数,如果有任何其他内容(包括文档const char*后跟一些参数),则将调用省略号函数。

不幸的是,这似乎是允许您将 va_args 传递给子函数的可能解决方案的范围,在我的示例中为vsnprintf.

接受我自己的答案可能是一种不好的形式,即使它是回答所提出问题的答案。

于 2010-09-03T17:42:59.070 回答