创建带有验证和语法突出显示的自定义printf
我希望能够创建一种日志记录机制,它非常简单地将参数传递给printf,但我需要语法突出显示和输入验证。这是我到目前为止获得的Log
命名空间的大纲。
#pragma once
#include "pch.h"
namespace Log
{
// Determines whether to create the console window or not.
// Turn off for before release.
extern bool CreateConsoleWindow;
// Initialisation.
extern bool Init();
// Writes a line to the console, appended with
.
extern void WriteLine(const char* _Format, ...);
// Writes a line to the console, with a [Debug] prepend.
extern void DebugInfo(const char* _Format, ...);
// Writes a line to the console, with a [Server] prepend.
extern void ServerInfo(const char* _Format, ...);
// Destruction.
extern bool Dispose();
}
这是我对Log::WriteLine(_Format, ...)
的实现:
// Writes a line to the console, appended with
.
void WriteLine(const char* _Format, ...)
{
// Print the message.
char buffer[4096];
va_list args;
va_start(args, _Format);
auto rc = vsnprintf(buffer, sizeof(buffer), _Format, args);
va_end(args);
// Append the new line.
printf("
");
}
但当我这样做时,我会丢失输入字符串中的所有验证和语法突出显示,这对于初学者来说是必不可少的。例如:
auto hash = Crypto::GetHash(syntax[1].c_str()); // unsigned long, by way of multiple nested macros.
printf("Hash: %d", hash);
这将以橙绿色显示%d
,并且在printf
中的hash
上会有一个警告,说它不会显示,因为它需要%lu
。我依靠VS来教我哪里做错了,这样我就能从错误中吸取教训。
如果我对我的函数执行相同的操作:
auto hash = Crypto::GetHash(syntax[1].c_str()); // unsigned long, by way of multiple nested macros.
Log::WriteLine("Hash: %d", hash);
则%d
与字符串的其余部分是相同的棕色,没有验证错误。
这两件事是必不可少的。有什么方法可以让这件事正常进行吗?在经历了大约十年的.NET经验后,我才开始接触C++,学习曲线是巨大的。我想我应该从基础开始,只需要一个简单的日志记录系统,这样我就可以随时以一种干净而优雅的方式看到输出的内容。在C#中,这只需要几分钟的时间就可以完成,但在C++中,四个小时后的今天,我在Firefox的三个窗口中打开了大约40个选项卡,我仍然在拼命地撞着可能遇到的每一堵砖墙。
我已尝试在命名空间内定义宏:
#define WriteLine(_Format, ...) printf(_Format, __VA_ARGS__)
但IS表示找不到printf。我尝试过使用_Printf_format_string_
:
extern int _cdecl DebugInfo(_Printf_format_string_ const char* _Format, ...);
但这并没有什么不同。我已尝试__attribute__((format(printf, 1, 2)))
extern void WriteLine(const char* _Format, ...) __attribute__((format(printf, 1, 2)));
但这引发了一大堆关于预期{
、unexpected identifier
和expected a declaration
的错误,但没有说明位置、原因或方式。
对于如此基本的请求,我是否对C++要求过高?
解决方案
现代代码编辑器和IDE(以及编译器!)提供对超出C++和C语言范围的printf
函数族的额外支持。最好的例子是对格式字符串进行扩展的错误检查,这在C中根本不能完成,在C++中只需费很大的力气。只需稍加修改即可实现目标。
<iostreams>
解决方案
By default, std::cout
is synchronized with printf
,所以您实际上可能不需要直接处理printf
。有许多解决方案可以帮助您使用ioStreams库框架实现各种结果,尽管使用起来有点麻烦。它是类型安全的,因此不需要额外的IDE支持即可直接显示错误。
请注意,IOStreams因其复杂性和易用性而名声不佳。
类型安全变量模板
您可以沿着lines of:
使用可变模板
void add_format_specifier(std::ostream& out, int const&) {
out << "%d";
}
void add_format_specifier(std::ostream& out, char const*) {
out << "%s";
}
void add_format_specifier(std::ostream& out, hex const&) {
out << "%x";
}
template<typename... Args>
void WriteLine(Args&&... args) {
std::ostringstream format;
((void)0, ..., add_format_specifier(format, args));
printf(format.str().c_str(), args...);
}
该解决方案是类型安全的,并且经过编译器检查,同样不依赖特殊大小写printf
。就我个人而言,我建议走这条路,因为它结合了最大限度的灵活性和简单的类型检查-并取消了格式说明符;)。您可能希望确定使用printf
作为低级原语是否真的是您希望在此处执行的操作,但它可以很容易地交换为其他输出方法。
格式字符串的编译时检查
possible添加格式说明符的自定义编译时检查,然后您可以static_assert
。您需要的基本构建块是一个可变模板,它还接受您的格式说明符(基本上,您需要保留变量函数中的类型),并且它以如下方式调用格式说明符:
template<std::size_t N>
consteval bool check_format(char const (&format)[N], std::size_t i) {
for(; i < N; ++i) {
if(format[i] == '%') {
if(i + 1 >= N) {
return false;
}
if(format[i + 1] == '%') {
++i; // skip literal '%'
} else {
return false; // no more specifiers expected
}
}
}
return true;
}
template<std::size_t N, typename T, typename... Args>
consteval bool check_format(char const (&format)[N], std::size_t i) {
for(; i < N; ++i) {
if(format[i] == '%') {
if(i + 1 >= N) {
return false; // unterminated format specifier
}
if(format[i + 1] == '%') {
++i; // skip literal '%'
} else {
if constexpr(std::is_same_v<T, int>) {
// quickly check if it is an acceptable integer format specifier
if(format[i + 1] != 'd' && format[i + 1] != 'x') {
return false;
} else {
return check_format<N, Args...>(format, i + 2);
}
} else {
return false; // unknown format specifier
}
}
}
}
return false;
}
有关更多上下文,请参阅here。(注意:本例依赖于C++20consteval
说明符,您的编译器可能不支持该说明符。constexpr
也可以达到类似的效果。)
此解决方案将给您带来编译时错误,但不会突出显示语法-C++不会影响您的IDE绘制字符串的方式(尚;))。
宏解决方案
虽然您的宏#define WriteLine(_Format, ...) printf(_Format, __VA_ARGS__)
实际上不允许您执行除重命名printf
以外的所有操作,但它可以工作,前提是用户代码也包含<cstdio>
。如果您为该宏提供了标题,您可能只希望在其中添加包含项以便于使用。
只需要做一点微小的改进,这样对于WriteLine("abc")
形式的调用的宏also works:
#include <cstdio>
#define WriteLine(_Format, ...) printf(_Format __VA_OPT__(,) __VA_ARGS__)
相关文章