2010-10-27 25 views
2

Ma bibliothèque offre un point de rappel où les utilisateurs de ma bibliothèque peuvent s'inscrire pour obtenir des informations. La forme générale du rappel est un int suivi de divers paramètres dont le type dépend de la valeur int. Par conséquent, j'ai défini le type de rappel et la fonction pour le définir comme suit. Dans ma bibliothèque, j'appelle cette fonction tout au long, étant la fonction définie par l'utilisateur, ou la fonction par défaut que je fournis. Ça marche.Quoi de mieux pour les paramètres d'une fonction de rappel C: va_list, ou ellipsis?

Maintenant, je voudrais ajouter un niveau d'indirection, pour pouvoir appeler plusieurs callbacks enregistrés. Le problème est que ma fonction de rappel interne qui prend encore des paramètres d'ellipse, doit également appeler la fonction de rappel avec ellipse. En conséquence, ma fonction interne doit interpréter le type, déballer les paramètres du va_list et les donner comme paramètres à la fonction 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); 
} 

Mais, dans la mise en œuvre du rappel de l'utilisateur, il y aura aussi le même usage va_list, et l'interprétation des arguments, selon la valeur de type. La solution consiste à passer directement le va_list en tant qu'argument à la fonction de rappel, ce qui rend l'implémentation de la fonction de rappel interne évidente.

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

Ma question est: est-il une bonne pratique de définir un type de fonction de rappel qui prend va_list comme argument? Je peux définir mon type de fonction de rappel comme ci-dessus, mais quels sont les avantages et les inconvénients par rapport à la définition en haut?

Répondre

3

Je suppose qu'il existe un nombre fini et connu de types? Si oui, pourquoi ne pas utiliser les énumérations et les syndicats pour un certain niveau de sécurité de type, qui soit dit en passant aussi résoudre votre problème:

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); 

En fonction de la différence de taille des arguments, il pourrait être une bonne idée de passer un pointeur à la place . Vous pouvez également fournir des macros pour fournir une syntaxe plus agréable pour l'invocation de rappel, mais si elles ne sont jamais appelés à partir de votre bibliothèque, il pourrait ne pas être la peine:

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

est assez court lui-même.

2

Je voudrais aller avec la version va_list. L'utilisateur est déjà confronté à la frayeur de traiter avec va_list de sorte que vous ne sauvegardez rien en les dissimulant. En outre, l'utilisation de la liste plutôt que la tentative de repasser les arguments réduira l'utilisation de la pile. Si vous deviez repasser les arguments à leur fonction, cela ferait une nouvelle copie de tous ces arguments, mais en passant le type va_list, vous utiliserez simplement l'instance de ces arguments déjà sur la pile. Enfin, votre code sera à la fois plus simple (si je comprends bien le problème) et vous permettra d'économiser à chaque utilisateur d'avoir à appeler va_start et va_end (qui ne changera probablement pas le code de sortie dans leur fonction beaucoup pour la plupart des implémentations de stdarg) mais sinon ils doivent tous taper ces appels et (selon la façon dont stdarg sont implémentés sur la plate-forme) ils devront s'assurer qu'ils appellent réellement va_end avant de revenir (facile à manquer si retournant tôt en cas de une erreur).