如何解决实现可变参数函数的可选参数?
我有一个功能:
log(const char *domain,int log_level,const char *fmt,...)
我希望第一个和第二个参数是可选的,因此可以进行以下调用:
log("SYSTEM-A",1,"Example %s","...message");
log(1,"...message");
log("Example %s","...message");
我已经阅读了有关简洁的宏技巧的文章,但是它们(几乎?)都依赖于尾随参数以在辅助宏中“脱颖而出”:
HELPER_SELECT(_1,_2,_3,func,...) func
但是我不能使用这个方法,因为 log()
可以接受任意数量的可变参数。这有可能以某种方式克服吗?使用 _Generics
,也许?
解决方法
(1) log("SYSTEM-A",1,"Example %s","...message");
(2) log(1,"...message");
(3) log("Example %s","...message");
据我所知:
- (1) 在它的第一个参数中没有
%
。 - (2) 第一个参数是 int
- (3) 的参数中有
%
。
您可以:
- 在参数数量上重载
log
宏 - 如果一个参数
- 选择 (3)
- 其他
- _第一个参数的通用
- 如果第一个参数是 int
- 选择 (2)
- 其他
- 打电话给
_log_wrapper(const char *arg,...)
- 检查是否
strchr(arg,'%')
- 如果是,则调用 (3) 的 va_list 版本
- 如果没有,则调用 (1) 的 va_list 版本
- 检查是否
- 打电话给
一个可能的实现如下所示:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
void vlog_domain(const char *domain,int log_level,const char *fmt,va_list va) {
printf("domain\n");
}
void vlog_level(int log_level,va_list va) {
printf("level\n");
}
void vlog_normal(const char *fmt,va_list va) {
printf("normal\n");
}
void _log_wrapper(int type,...) {
va_list va;
va_start(va,type);
if (type == 1) {
int log_level = va_arg(va,int);
const char *fmt = va_arg(va,const char *);
vlog_level(log_level,fmt,va);
} else {
const char *arg = va_arg(va,const char*);
if (!strchr(arg,'%')) {
const char *domain = arg;
int log_level = va_arg(va,int);
const char *fmt = va_arg(va,const char*);
vlog_domain(domain,log_level,va);
} else {
const char *fmt = arg;
vlog_normal(fmt,va);
}
}
va_end(va);
}
#define _log_1(_1) vlog_normal(_1) // TODO
#define _log_2(_1,...) _log_wrapper( \
_Generic((_1),int: 1,char *: 2),_1,##__VA_ARGS__)
// this implementation supports max ca. 10 arguments
#define _log_N(_9,_8,_7,_6,_5,_4,_3,_2,_0,N,...) _log_##N
#define log(...) _log_N(__VA_ARGS__,2,1)(__VA_ARGS__)
int main() {
log("SYSTEM-A","...message"); // domain
log(1,"...message"); // level
log("Example %s","...message"); // normal
}
这些是在编写界面上花费的一些时间,下一个开发人员很可能无论如何都不会理解,并且将不得不重写和重构整个代码。相反,我建议尽可能清楚并编写尽可能简单易懂的代码,并命名您的函数:
logd("SYSTEM-A","...message");
logl(1,"...message");
log("Example %s","...message");
并完成它。
检查其他项目如何使用“域+日志级别”解决日志记录(听起来像 syslog()
严重性和设施....)看看其他项目是如何解决日志记录接口的。在我看来,我很喜欢 zephyr project solved logging,而且它是开源的,所以请查看它的源代码。
这在 C 中是不可能的 - 可变参数函数解决了一次这个问题,但你试图解决它两次。
考虑使用简单的选项结构。让所有选项空值等价于 0
(或有一个默认的初始化器),这样调用者就不需要记住任何哨兵值。
struct loggeropts {
const char *domain;
int level;
};
void logger(struct loggeropts *opts,...) {
if (opts) {
if (opts->domain) { /* */ }
if (opts->level) { /* */ }
}
/* impl */
}
int main(void) {
struct loggeropts opts = { .domain = "SYSTEM-A" };
logger(&opts,"#Example %u %s",42,"information");
logger(NULL,44,"different information");
}
如果不经常使用可选参数,您可以将调用隐藏在宏后面。
#define logger(string,...) logger_with_opts(NULL,string,__VA_ARGS__)
void logger_with_opts(struct loggeropts *opts,...);
或者,在格式字符串中有一个特殊部分来标识传递的选项,并确保它们在通常的可变参数之前传递。记住在继续传递之前移动您的 fmt
指针。不过,这确实看起来很脆弱,并且有额外的开销。
logger("{{@d@l}}Example %s","SYSTEM-A","...message");
logger("{{@d}}Example %s","SYSTEM-B","...message");
我最有可能的建议是,除了通用功能之外,只需使用特定于域的日志记录功能即可。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。