如何在类成员函数内的 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 中获得编译警告的主要内容,如果未能解决你的问题,请参考以下文章