创建具有验证和语法突出显示的自定义 printf
Creating a custom printf with validation and syntax highlighting
我希望能够创建一个日志记录机制,它非常简单地将参数传递给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 rn.
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 rn.
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("rn");
}
但是当我这样做时,我从输入字符串中丢失了所有验证和语法突出显示,这对于初学者至关重要。例如:
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__)
但据说找不到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(和编译器!(为printf
系列函数提供了超出C++和C语言范围的额外支持。一个典型的例子是对格式字符串的扩展错误检查,这在 C 中根本无法完成,而且在 C++ 中只能带来很大的痛苦。有几种方法只需稍作修改即可实现您的目标。
<iostreams>
解决方案
默认情况下,std::cout
与printf
同步,因此您可能实际上不需要直接处理printf
。对于如何使用iostreams库框架实现各种结果,有很多解决方案,即使使用起来有点麻烦。它是类型安全的,因此不需要额外的 IDE 支持即可直接显示错误。
请注意,iostreams 因其复杂性和易用性而名声不佳。
类型安全可变参数模板
您可以按以下方式使用可变参数模板:
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
作为低级原语是否真的是您想要在此处执行的操作,但它很容易交换为其他输出方法。
格式字符串的编译时检查
可以添加格式说明符的自定义编译时检查,然后可以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;
}
有关更多上下文,请参阅此处。(注意:此示例依赖于 C++20consteval
说明符,编译器可能不支持该说明符。使用constexpr
可以实现类似的效果。
此解决方案将为您提供编译时错误,但不突出显示语法 - C++不会影响 IDE 绘制字符串的方式(但;)(。
宏解决方案
虽然您的宏#define WriteLine(_Format, ...) printf(_Format, __VA_ARGS__)
实际上并不允许您在实现重命名printf
之外的任何内容方面做那么多事情,但前提是用户代码还包括<cstdio>
。如果您提供了包含该宏的标头,则可能希望仅在其中添加包含内容以方便使用。
只需要进行微小的改进,因此宏也适用于形式为WriteLine("abc")
的调用:
#include <cstdio>
#define WriteLine(_Format, ...) printf(_Format __VA_OPT__(,) __VA_ARGS__)
- Qt自定义QPush按钮未显示在布局上
- 创建具有验证和语法突出显示的自定义 printf
- 如何使用IExecuteCommand和动词在上下文菜单外壳扩展中显示本地化文本和自定义图标?
- C++未显示自定义异常消息
- QlineEdit:显示一个处理过的文本,而不是输入的文本,而是保留它(自定义回声模式)
- 隐藏自定义应用程序窗口,但应在任务栏上显示应用程序图标(不是作为托盘图标)
- 在自定义树视图中显示QCOMBOBOX向下箭头
- LLDB调试器 - 定义自定义类型显示
- 自定义 OpenGL 缓冲区类不显示任何内容
- Qt自定义树模型显示正确,但有缺陷且速度慢
- 禁用 CListCtrl 自定义绘制函数中的行或将其显示为灰色
- 自定义光标未显示在自定义按钮上
- 如何在 Win32 API 的对话框中显示自定义消息
- 如何在c++中设置智能感知来显示自定义数据类型
- 在TCLAP中显示自定义帮助信息
- Boost日志不显示自定义时间戳
- 当运行时库不存在时显示(自定义"required"错误?
- 在布局中显示自定义小部件
- OpenFileDialog C#显示自定义文本
- 显示自定义图像并能够捕捉鼠标输入