代码之家  ›  专栏  ›  技术社区  ›  dash-tom-bang

函数重载,其中参数只与椭圆不同

  •  9
  • dash-tom-bang  · 技术社区  · 14 年前

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

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

    然后logmessage执行 snprintf 进入一个新的缓冲区,然后向任何日志目标显示该消息;printf、outputDebugString等。

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

    那么-考虑到我的函数原型,我是否应该能够基于椭圆的存在来超载呢?换句话说,我是否可以假设我可以做如下的事情:

    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"); 结果在日志消息中得到“buffer not 1007.732873e10ull”。

    编辑: 当我的例子被回答为“不要这样做”时,问题本身能被回答吗?

    4 回复  |  直到 10 年前
        1
  •  2
  •   Jesse Elliott    10 年前

    我对这个问题最初的回答很有启发,但现在有了一些改进。

    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*。

        2
  •  3
  •   John Kugelman Michael Hodel    14 年前

    我还认识到,如果输出消息中有百分比符号,这个方法将失败,因为snprintf将尝试处理va_参数。

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

    老实说,当我意识到如果有人偶然说 Error("Buffer not 100% full"); 结果在日志消息中得到“buffer not 1007.732873e10ull”。

    我认为你最好还是和C++的人相处。在Java方法中,通常检查有效参数并在传递无效值时抛出异常。在C++中,你只需让打电话的人在脚上开枪。最好让他们写下来 100%% 而不是跳过去保护他们不学习如何正确调用你的函数。

        3
  •  2
  •   smilingthax    10 年前

    在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)
    ...
    
        4
  •  1
  •   dash-tom-bang    14 年前

    好吧,我想我想出了这个问题的解决办法。

    事实是你 不能 仅根据椭圆是否有参数进行重载。也就是说,不能有只有椭圆存在时才会变化的签名的函数。

    然而,它 可以做一些像我问的如果我放弃 const char* 椭圆原型的参数。即。

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

    是不含糊的,但现在您必须假设第一个参数是 常量字符 但很可能不是。接受约翰·库格曼的建议,也许没关系;你记录下允许的参数,用户要小心。如果只有一个 常量字符 ,如果还有其他内容,包括文档中的 常量字符 然后是一些参数。

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

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