C++ 中的 printf 包装器

Printf wrapper in C++

提问人:mj2 提问时间:10/18/2023 最后编辑:mj2 更新时间:10/18/2023 访问量:105

问:

我正在尝试构建一个病态函数,它基本上是 printf 的包装器。

我需要使用 vsnprintf 构建一个 char* 缓冲区。这是执行此操作的函数的签名:char* make_buffer_fmt(char foo, const char* fmt, ...)

现在,当然,还有另一个函数围绕着这个函数:int write_fmt(int fd, char foo, const char* fmt, ...)

另一个环绕:write_fmtint log(const char* fmt, ...)

如何将参数包从 传递到 ? 我不想每次都“手动”抓取va_list。(恐怕没有别的办法了?logmake_buffer_fmt

你可能会告诉我参数包,va_list不能很好地混合,但我对C++有点陌生,老实说,当它有时编译时,我已经很高兴了。我现在拥有它的方式,我在我需要它的地方得到了va_list,但是,当然,它只是读取一些随机内存,因为va_list不会传递。make_buffer_fmt

我喜欢这三个点,因为它复制了 printf 的行为,这是最终的目标。当我将 va_list 参数更改为 args 时,它不再希望我的 char* :(。...

编辑2:我认为我的问题不是很清楚,所以这里有一些代码:

int log_info(const char *fmt, ...) { // Is this how I have to do it? 
    va_list va;                      // Problem I have with this, it that write_fmt doesn't have the '...'
    va_start(va, fmt);               // Instead, I have to pass the va_list to write_fmt, which is not what I want. 
    return write_fmt(LOG_FILENO, LOG_INFO, fmt, va); // Because I want to be able to call write_fmt like this: write_fmt(LOG_FILENO, LOG_INFO, "Hello %s", "World");
    va_end(va);
}

编辑:这是解决方案,但它丑陋且不切实际,并且有很多样板代码:将变量参数传递给另一个接受变量参数列表的函数

C++ printf 包装器

评论

0赞 KamilCuk 10/18/2023
没有吗?如果是这样,您应该考虑访问上游并提交错误。make_buffer_fmt_v(...., va_list)
0赞 mj2 10/18/2023
也许这只是我对 python 期望的一个症状
3赞 KamilCuk 10/18/2023
如果您不问 XY 问题,而是指定您正在使用的确切函数和库以及您想要实现的确切目标,那也会更容易。
1赞 Raymond Chen 10/18/2023
寻找一个版本并使用它。如果没有,请提出要求。如果你迫不及待地想有人为你写,那就自己写吧。如果你无法更改代码(它在别人的库中),你可以使用可变参数模板函数:va_listmake_buffer_fmttemplate<typename...Args> int log_info(const char*fmt, Args...args) { return write_fmt(LOG_FILENO, LOG_INFO, fmt, args...); }
1赞 Chris_F 10/18/2023
我假设您这样做是一种教育练习,而不是出于任何实际原因,否则我会指出 libfmt 已经存在,std::format 和 std::p rint 也是如此,它们病态得多。你对可变参数函数所做的很大程度上是 C 的事情。在 C++ 中,您更喜欢使用模板可变变量

答:

0赞 Useless 10/18/2023 #1

如何将参数包从 传递到 ?logmake_buffer_fmt

首先,您需要有一个参数包。

这是一个带有参数包的 C++ 可变参数函数模板:

template <typename... Args>
int log(const char *fmt, Args&&... args)
{
    return write_fmt(LOG_FILENO, LOG_INFO, fmt, std::forward<Args>(args)...);
}

你根本不使用,因为这是为了在你丢失所有类型信息后处理 C 样式的可变参数函数参数。在 C++ 中,您不需要首先丢弃该类型信息(或者至少在调用 C 样式函数之前不需要,例如va_listvsnprintf)

唯一(潜在的)缺点是所有这些函数都必须在标头中内联定义。由于他们并没有真正做任何似乎值得隐藏的事情,我认为这不是问题。


请注意,在原始版本中,无论如何都不适合使用参数进行调用。这与 Python 中的参数序列不同,它不能扩展回可变长度的参数列表。它只是一个单一的结构。int write_fmt(int fd, char foo, const char* fmt, ...)va_list*args

这就是为什么标准库同时具有 snprintfvsnprintf 的原因:

int snprintf(char* buffer, std::size_t buf_size, const char* format, ...);
int vsnprintf(char* buffer, std::size_t buf_size, const char* format, std::va_list vlist);

无论如何,应该是一个类似于上述的完美转发直通,然后会使用其扩展的参数包进行调用:write_fmtmake_buffer_fmtsnprintf

template <typename... Args>
int make_buffer_fmt(char *buffer, size_t bufsize, const char *fmt, Args&&... args)
{
    return snprintf(buffer, bufsize, fmt,  std::forward<Args>(args)...);
}

或者明确(尽管这样做确实没有任何好处)vsnprintfva_list

int make_buffer_fmt(char *buffer, size_t bufsize, const char *fmt, ...)
{
    va_list va;
    va_start(va, fmt);
    int rc = vsnprintf(buffer, bufsize, fmt, va);
    va_end(va);
    return rc;
}

或者 std::format 如果您已准备好迎接 21 世纪,并且不介意更改格式字符串语法(或动态分配)。