如何在类成员函数内的 printf 中获得编译警告

Posted

技术标签:

【中文标题】如何在类成员函数内的 printf 中获得编译警告【英文标题】:how to get compilation warnings like in printf inside class member function 【发布时间】:2018-07-17 12:11:01 【问题描述】:

我想编写记录器,它会使用 printf 函数将日志打印到控制台。

假设我有以下代码:

class Logger 
public:
    Logger(std::string header = "") : header_(header) 
    template<class ...Args>
    void LogInfo(const char* message, Args... args);
private:
    std::string header_;
;

template<class ...Args>
void Logger::LogInfo(const char* message, Args... args) 
    printf(message, args...);

这记录很好,但问题是当我打电话时:

const char* s = "Monty Python";
Logger logger("[Header]");
logger.LogInfo("%d", s);

logger 在没有任何警告的情况下打印指针值,而 printf 调用会导致错误(使用我的编译标志)

error: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘const char*’ [-Werror=format=]
 printf("%d", s);

整个代码的重点是我想在 LogInfo 调用期间得到这个错误。 我怎么能做这样的事情?

【问题讨论】:

做是不可能的。 printf 在运行时被解析。看看***.com/questions/20424625/… 我不确定可变参数模板是否可行,但您可以尝试 format common function attribute (请记住,成员函数对于 this 指针有一个隐藏的第一个参数,这意味着列表中的第一个参数实际上是 format 属性的 second 参数)。 但是,如果您想要一个正确的类型安全的“printf”变体(您的仍然不是),那么请考虑使用诸如Boost format 之类的库。 我会尝试使用一个属性,但让我非常困惑的是独立的 printf 会导致错误,但 Wrapped 不会。 为什么要使用printf格式?使用流式操作符并享受类型安全。 【参考方案1】:

回退到printf 函数族和格式字符串会给你带来很多关于类型安全的问题 你可能会更好地使用现代 C++ 流机制。当然,你不会真的想这样记录:

logger << "first: " << x << "second: " << y << commit;

所以想出一些替代方法来避免需要适当格式字符串参数的问题;思路如下:

参数在格式字符串的指定位置一个接一个地插入 插入位置由字符模式%#标识 pattern %## 抑制参数插入并替换为插入模式作为字符串

缺点:我们必须自己进行解析:

void logInfo(char const* message)

    char const* m = message;
    while((m = strchr(m, '%')))
    
        if(*++m == '#')
        
            if(*++m != '#')
            
                std::cout.write(message, m - message - 2);
                std::cout << "<missing argument>";
            
            else
            
                std::cout.write(message, m - message);
                ++m;
            
            message = m;
        
    
    std::cout << message << std::endl;


template<typename A, typename ... AA>
void logInfo(char const* message, A a, AA ... aa)

    char const* m = message;
    while((m = strchr(m, '%')))
    
        if(*++m == '#')
        
            if(*++m != '#')
            
                std::cout.write(message, m - message - 2);
                std::cout << a;
                return logInfo(m, aa...);
            
            std::cout.write(message, m - message);
            message = ++m;
        
    
    std::cout << message << std::endl;

当然,还有一些通用的代码,留给你优化,只是为了想法......

在以下示例中运行良好:

logInfo("t1");
logInfo("t2", 7);
logInfo("t3: %#", 12);
logInfo("t4: %#%##", 10);
logInfo("t5: %#%%#", 12);
logInfo("t6: %#% baz", 10);
logInfo("t7 1: %# 2: %# 3: %#", 10, 12);

您可以添加更多格式选项,例如最小输出宽度、填充字符、精度等——就像 printf 提供的一样...

当然,这个答案与完全您的问题(“如何产生警告”)不匹配,相反,它只是使警告过时......

【讨论】:

【参考方案2】:

好吧,我希望别人这么说,但我想我会是那个带来宏的人......

#define LogInfo(logger, format, ...) printf("%s " format, logger.header().c_str(), __VA_ARGS__);

为了说明可以实现什么,我假设您想在每一行添加记录器标题。这只是一个例子。

你会这样使用它:

#include <cstdlib>
#include <string>
#include <iostream>

class Logger 
public:
    Logger(std::string header = "") : header_(header) 
    std::string const& header() const  return header_; 
private:
    std::string header_;
;

#define LogInfo(logger, format, ...) printf("%s " format, logger.header().c_str(), __VA_ARGS__);

int main()

    const char* s = "Monty Python";
    Logger logger("[Header]");
    //LogInfo(logger, "%d", s); // error: format '%d' expects argument of type 'int', but argument 3 has type 'const char*' [-Werror=format=]
    LogInfo(logger, "%s", s); // [Header] Monty Python

演示:http://coliru.stacked-crooked.com/a/ad698776f2b0ed4f

【讨论】:

【参考方案3】:

正如 cmets 中指出的,printf 格式错误可以通过format attribute 使用。但是您必须为此松散 vardiag 模板,或者从 vardiac 模板函数向简单的 C vardiac 函数添加另一个间接级别。

格式说明符在 gcc(和其他编译器)的 printf 定义中是隐含的,对于许多其他类似 printf 的函数是显式的。例如

extern int vsnprintf (char *__restrict __s, size_t __maxlen,
                  const char *__restrict __format, _G_va_list __arg)
 __THROWNL __attribute__ ((__format__ (__printf__, 3, 0)));

由于该属性, vsnprintf 将给出与普通 printf 相同的警告。请参阅链接文档以了解如何为您的函数指定格式属性(在失去 vardiac 模板之后)。注意:转换为普通的 vardiac 函数意味着您必须使用编译器的 varargs 宏调用 vprintf。

【讨论】:

以上是关于如何在类成员函数内的 printf 中获得编译警告的主要内容,如果未能解决你的问题,请参考以下文章

在类中使用自定义排序时出现编译错误 [重复]

C ++错误C2600:无法定义编译器生成的特殊成员函数(必须首先在类中声明)[重复]

类成员函数中的静态本地在类重新分配中幸存下来?

类内的内联函数成员

如何在类的成员函数中调用复制构造函数?

类成员的编译顺序之嵌套类型对类的影响