C宏中的可变函数转换
我想将此代码块放入C宏:C宏中的可变函数转换,c,C,我想将此代码块放入C宏: void* instance = frm_config_get_instance("inside"); int (*func)(void* inst, double value) = frm_config_get_function("comp123_work2"); int res = func(instance, 0); 所以我可以这样使用它: res = FRM_CONFIG_CALL("inside", "comp123_work2", 0.0) 这就是我目前
void* instance = frm_config_get_instance("inside");
int (*func)(void* inst, double value) = frm_config_get_function("comp123_work2");
int res = func(instance, 0);
所以我可以这样使用它:
res = FRM_CONFIG_CALL("inside", "comp123_work2", 0.0)
这就是我目前能想到的:
#define FRM_CONFIG_CALL(instance, function, ...) \
((int*)(void*, __VA_ARGS__))frm_config_get_function(function) \
(frm_config_get_instance(instance), __VA_ARGS__)
有什么想法吗?你要做的事情可能行不通。但您可以创建一个“轻”版本:
#define FRM_CONFIG_FUNC(function, ...) \
((int(*)(void*, __VA_ARGS__))frm_config_get_function(function))
#define FRM_CONFIG_CALL(func, instance, ...) \
(func)(frm_config_get_instance(instance), __VA_ARGS__)
像这样使用它
res = FRM_CONFIG_CALL(FRM_CONFIG_FUNC("comp123_work2", double), "inside", 0.0)
免责声明:我还没有测试过它,但它可能会给你一个想法。你试图实现的方法意味着程序员必须事先知道每个“动态可调用”函数的参数数量和类型,这就违背了一开始就有这种机制的意义。例如,您必须知道
comp123\u work2
接受类型为double
的单个参数
虽然这种违反OOP原则的行为可能不会给您带来太多麻烦,但实际上这意味着您将用编译错误换取难以找到的运行时问题。读取通过宏传递给函数的不正确的varargs值是一个即将发生的灾难
有几种方法可以帮助您实现现在的目标:
1。将参数传递为void*
这种方法并没有提供更多的编译时安全性,但对于一个毫无戒心的代码维护者来说,这并不奇怪
在C中处理不同参数类型的常用方法是通过void
指针传递自定义参数,即:
typedef int(*voidvoidfunc_t)(void*, void*);
extern void* frm_config_get_instance(const char* name);
extern voidvoidfunc_t frm_config_get_function(const char* name);
然后每个实现都可以强制转换为它想要的任何内容:
int printf_wrapper(void * ch, void * p)
{
double val = *(double*)p;
return printf(ch, val);
}
voidvoidfunc_t frm_config_get_function(const char* name)
{
return printf_wrapper;
}
你可以简单地用以下方式来称呼它:
void * parameter = frm_config_get_instance("inside");
voidvoidfunc_t func = frm_config_get_function("comp123_work2");
double val = 0.0;
int result = func(parameter, &val);
2。变量args函数指针
另外,请注意,如果所有函数都使用varargs,那么也可以定义变量参数函数指针。同样,没有编译时安全性(与C中的任何变量args函数一样):
然后:
void * parameter = frm_config_get_instance("inside");
varargfunc_t func = frm_config_get_function("comp123_work2");
int result = func(parameter, 0.0);
但是,您的所有“worker”函数每次都必须“解包”参数,例如:
int printf_wrapper(void * ch, ...)
{
va_list va;
va_start(va, ch);
int ret = vfprintf(stdout, ch, va);
va_end(va);
return ret;
}
varargfunc_t frm_config_get_function(const char* name)
{
return printf_wrapper;
}
struct arglist = al;
int rv;
al.feed_double(0.0);
al.feed_int(4);
FUNC_MAGIC("foo", "bar", al, rv);
assert(rv != NOT_VALID_ARG_PACKING);
int pack_arg_list(struct arglist* al, const char* func, const char* inst, ...) {
// ...
elif (!strcmp("foo", func) {
va_start(vl, 2); // I just knew "foo needed 2 args"
tmp_dbl = va_arg(vl, double);
al_tmp.pack_double(tmp_dbl);
tmp_int = va_arg(vl, int);
al_tmp.pack_int(tmp_int);
}
}
3。为每种参数类型提供单独的功能
如果没有那么多参数,实现编译时类型安全的一种方法是为每个参数类型使用单独的函数,即:
typedef int(*intfunc_t)(void*, int);
typedef int(*floatfunc_t)(void*, float);
typedef int(*stringfunc_t)(void*, const char*);
这样您就不会以错误的方式解释args:
int printf_wrapper(void * ch, float val)
{
return printf(ch, val);
}
floatfunc_t frm_config_get_float_function(const char* name)
{
return printf_wrapper;
}
这意味着您在调用frm\u config\u get\uxxxxx\u函数后有一个强类型的fn指针
:
void * parameter = frm_config_get_instance("inside");
floatfunc_t func = frm_config_get_float_function("comp123_work2");
int result = func(parameter, 0.0f);
4。在GLib中使用变量联合类型,如GValue
这可能不太常见,并且没有提供完整的类型安全性,但它至少提供了元数据,可以帮助您在错误调用时抛出错误。其思想是将多个值合并为一个值类型,以便您的函数可以检查参数是否符合其期望值
int printf_wrapper(void * ch, GValue *val)
{
// this checks that the parameter has the correct type
if (!G_VALUE_HOLDS_FLOAT(val))
error();
float f = g_value_get_float(val);
return printf(ch, f);
}
typedef int(*gvalue_func_t)(void*, GValue *val);
extern void* frm_config_get_instance(const char* name);
extern gvalue_func_t frm_config_get_function(const char* name);
然后在运行时构造GValue
对象:
void * parameter = frm_config_get_instance("inside");
gvalue_func_t func = frm_config_get_function("comp123_work2");
GValue val = G_VALUE_INIT;
g_value_set_float(&val, 0.0f);
func(parameter, &val);
首先,正如之前所说的那样:目前看来,这似乎是一个奇怪的要求 这就是说,当然有时需要在运行时确定类型。然而,共识似乎是以一种特殊的方式构建您的候选人功能。而不是让callsite做一些特殊的事情。 我认为这样做的动机是明确的:如果作者给出了错误的参数,那么很容易在调用建议的宏的任何地方做坏事,即使它实现得很好 建议/考虑: 使用生成的代码或宏定义函数,这样,您就可以“查找”函数模板,而无需推断它 我遇到的大多数这种性质的解决方案都来自远程过程调用。这些方法的实现可能会有所帮助,但总结起来,它们遵循这种模式的一些变体:
- 将参数打包/序列化到容器中
- 编写包装器来解压缩这些文件,并根据函数的某些枚举调用底层函数
int printf_wrapper(void * ch, ...)
{
va_list va;
va_start(va, ch);
int ret = vfprintf(stdout, ch, va);
va_end(va);
return ret;
}
varargfunc_t frm_config_get_function(const char* name)
{
return printf_wrapper;
}
struct arglist = al;
int rv;
al.feed_double(0.0);
al.feed_int(4);
FUNC_MAGIC("foo", "bar", al, rv);
assert(rv != NOT_VALID_ARG_PACKING);
int pack_arg_list(struct arglist* al, const char* func, const char* inst, ...) {
// ...
elif (!strcmp("foo", func) {
va_start(vl, 2); // I just knew "foo needed 2 args"
tmp_dbl = va_arg(vl, double);
al_tmp.pack_double(tmp_dbl);
tmp_int = va_arg(vl, int);
al_tmp.pack_int(tmp_int);
}
}
这是可以实现的
如果可以使用va_列表从_宏(0.0,4)转换为提要版本,那么也可以使用理解“foo”重要性的函数调用。您可以使用此功能返回oneliner callsite,但您需要一些类似以下内容的代码:
int printf_wrapper(void * ch, ...)
{
va_list va;
va_start(va, ch);
int ret = vfprintf(stdout, ch, va);
va_end(va);
return ret;
}
varargfunc_t frm_config_get_function(const char* name)
{
return printf_wrapper;
}
struct arglist = al;
int rv;
al.feed_double(0.0);
al.feed_int(4);
FUNC_MAGIC("foo", "bar", al, rv);
assert(rv != NOT_VALID_ARG_PACKING);
int pack_arg_list(struct arglist* al, const char* func, const char* inst, ...) {
// ...
elif (!strcmp("foo", func) {
va_start(vl, 2); // I just knew "foo needed 2 args"
tmp_dbl = va_arg(vl, double);
al_tmp.pack_double(tmp_dbl);
tmp_int = va_arg(vl, int);
al_tmp.pack_int(tmp_int);
}
}
问题是如何最好地编写/维护这个函数。
我想不出一个不那么丑陋的方法你可以在C++中使用可变模板,但是我不认为你可以用C预处理器来做。有什么想法吗?您显示的代码有什么问题?它有效吗?你只需要一个?或者你会犯一些错误吗?问的问题和你一样多,会员也这么多,你真的知道得更好。也许是时候回顾一下了,重新阅读一下了?@Someprogrammerdude可能:有什么想法可以让它变成想要的宏形式。你真的需要单引号:
('inside','comp123_work2',…
?陷入麻烦的最佳方式:)。我会将其作为空指针传递。IMO-避免这种不可调试的宏。这两种想法都很好,但是vararg方法不是这样工作的。您必须使用的vprintf()
of手动解压被调用函数的参数。@glglglgl:是的,抱歉,已修复。@Groo-wow!非常感谢你!我理解您对编译时安全和动态调用的担忧。你对不同选择的解释是例外的。我使用SO已经有一段时间了,我记不起这么好的答案了。我希望很多人会像你一样,把这里再次变成一个快乐的地方。@Groo仍然需要更多地研究不同的选项,但我不再100%相信我会采用宏观解决方案。这让我陷入了某种困境,因为我要求C宏解决方案。@mark:谢谢!通常有许多不同的方法来解决这个问题