Что лучше для параметров функции обратного вызова C: va_list или многоточие? - PullRequest
2 голосов
/ 27 октября 2010

Моя библиотека предлагает точку обратного вызова, где пользователи моей библиотеки могут зарегистрироваться для получения информации.Общая форма обратного вызова - int, за которой следуют различные параметры, тип которых зависит от значения int.Поэтому я определил тип обратного вызова и функцию для его установки следующим образом.

typedef void (* callback_func_t)(int type, ...);
void set_callback_func(callback_func_t callback);

В моей библиотеке я все время вызываю эту функцию, являясь функцией пользовательского набора или предоставляемой по умолчанию, которую я предоставляю,Это работает.

Теперь я хотел бы добавить уровень косвенности, чтобы иметь возможность вызывать несколько зарегистрированных обратных вызовов.Проблема в том, что моя внутренняя функция обратного вызова, которая все еще принимает параметры многоточия, также должна вызывать функцию обратного вызова с многоточием.Как следствие, моя внутренняя функция должна интерпретировать type, распаковать параметры из va_list и передать их в качестве параметров функции callbacj.

void internal_callback(int type, ...) {
    va_list args;
    va_start(args, type);
    switch (type) {
    case 0: call_callback(type, va_arg(args, int)); break;
    // ...
    }
    va_end(args);
}

Но затем в пользовательской реализациипри обратном вызове также будет использоваться va_list и интерпретация аргументов в соответствии со значением type.Решением является прямая передача va_list в качестве аргумента функции обратного вызова, что делает реализацию внутренней функции обратного вызова очевидной.

typedef void (* callback_func_t)(int type, va_list args);

Мой вопрос: является ли хорошей практикой определение функции обратного вызоватип, который принимает va_list в качестве аргумента?Я могу определить свой тип функции обратного вызова, как указано выше, но каковы плюсы и минусы по сравнению с определением сверху?

Ответы [ 2 ]

4 голосов
/ 27 октября 2010

Я предполагаю, что существует конечное и известное количество типов?Если так, то почему бы не использовать перечисления и объединения для некоторого уровня безопасности типов, что, кстати, также решило бы вашу проблему:

enum callback_arg_type { INT_ARG, DOUBLE_ARG, VECTOR_ARG };

union callback_arg
{
    int as_int;
    double as_double;
    struct { double x, y, z; } as_vector;
};

typedef void (*callback_func_t)(
    enum callback_arg_type type, union callback_arg arg);

В зависимости от разницы размеров аргументов, было бы неплохо передатьуказатель вместоВы также можете предоставить некоторые макросы для обеспечения более приятного синтаксиса для вызова обратного вызова, но если они когда-либо вызываются только из вашей библиотеки, это может не стоить того:

union callback_arg arg = { .as_int = 42 };
callback_fn(INT_ARG, arg);

довольно короток сам по себе.

3 голосов
/ 27 октября 2010

Я бы пошел с va_list версией.Пользователь уже имеет дело со скудостью работы с va_list s, поэтому вы ничего не сохраняете, скрывая это от них.

Кроме того, использование списка вместо попытки перераспределения аргументов сократит стекиспользование.Если бы вам пришлось передать аргументы в их функцию, он сделал бы новую копию всех этих аргументов, но передача типа va_list просто использовала бы экземпляр этих аргументов, уже находящихся в стеке.

Наконец,ваш код будет проще (если я правильно понимаю проблему), и вы избавите каждого пользователя от необходимости вызывать va_start и va_end (что, вероятно, не сильно изменит выходной код в их функциях для большинства реализаций)из stdarg), но в противном случае все они должны будут набирать эти вызовы и (в зависимости от того, как stdarg s реализованы на платформе) им нужно будет убедиться, что они действительно вызывают va_end перед возвратом (легко пропустить при возвратерано в случае ошибки).

...