Подход, который вы пытаетесь реализовать, означает, что программист должен заранее знать число и тип параметров каждой «динамически вызываемой» функции, и это лишает смысла наличие такого механизма в первую очередь.Например, вы должны знать, что comp123_work2
принимает один параметр типа double
.
Хотя это нарушение принципов ООП может вас не беспокоить, на практике это означает, что вы будете обменивать ошибки компиляции на трудно обнаруживаемые проблемы во время выполнения.Чтение неверного значения 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 в C):
typedef int(*varargfunc_t)(void*, ...);
extern void* frm_config_get_instance(const char* name);
extern varargfunc_t frm_config_get_function(const char* name);
И затем:
void * parameter = frm_config_get_instance("inside");
varargfunc_t func = frm_config_get_function("comp123_work2");
int result = func(parameter, 0.0);
Но тогда все ваши "рабочие" функции должны будут "распаковывать "args каждый раз, что-то вроде:
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;
}
3.Наличие отдельной функции для каждого типа параметра
Если у вас не так много параметров, способ обеспечения безопасности типов во время компиляции будет использовать отдельные функции для каждого типа параметра, то есть:
typedef int(*intfunc_t)(void*, int);
typedef int(*floatfunc_t)(void*, float);
typedef int(*stringfunc_t)(void*, const char*);
Тогда вы не можете неправильно интерпретировать аргументы:
int printf_wrapper(void * ch, float val)
{
return printf(ch, val);
}
floatfunc_t frm_config_get_float_function(const char* name)
{
return printf_wrapper;
}
Это означает, что у вас есть строго типизированный указатель fn после вызова frm_config_get_xxxxx_function
:
void * parameter = frm_config_get_instance("inside");
floatfunc_t func = frm_config_get_float_function("comp123_work2");
int result = func(parameter, 0.0f);
4.Использование альтернативного типа объединения, такого как GValue
в GLib
Это, возможно, немного менее распространено и не обеспечивает полную безопасность типов, но, по крайней мере, предоставляет метаданные, которые могут помочь вам вызвать ошибку в случаенеправильного вызова.Идея состоит в объединении нескольких значений с типом значения, чтобы ваша функция могла проверить, соответствует ли параметр ожидаемому.
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);