Valeurs par défaut dans une structure C

J’ai une structure de données comme celle-ci:

     struct foo {
         int id;
         int route;
         int backup_route;
         int current_route;
     }

et une fonction appelée update () utilisée pour demander des modifications.

   mise à jour (42, dont_care, dont_care, new_route);

c’est vraiment long et si j’ajoute quelque chose à la structure, je dois append un ‘dont_care’ à chaque appel pour mettre à jour (…).

Je pense à lui passer une structure, mais remplir la structure avec ‘dont_care’ est encore plus fastidieux que de simplement l’épeler dans l’appel de fonction. Puis-je créer la structure quelque part avec les valeurs par défaut de don’t care et simplement définir les champs qui m’intéressent après l’avoir déclarée comme variable locale?

     struct foo bar = {.id = 42, .current_route = new_route};
     mise à jour (& bar);

Quelle est la manière la plus élégante de transmettre uniquement les informations que je souhaite exprimer à la fonction de mise à jour?

et je veux que tout le rest par défaut à -1 (le code secret pour “Don’t Care”)

Alors que les macros et / ou les fonctions (comme cela a déjà été suggéré) fonctionneront (et pourraient avoir d’autres effets positifs (par exemple, les hooks de débogage)), ils sont plus complexes que nécessaire. La solution la plus simple et la plus élégante consiste à définir une constante que vous utilisez pour l’initialisation des variables:

const struct foo FOO_DONT_CARE = { // or maybe FOO_DEFAULT or something dont_care, dont_care, dont_care, dont_care }; ... struct foo bar = FOO_DONT_CARE; bar.id = 42; bar.current_route = new_route; update(&bar); 

Ce code n’a pratiquement pas de surcharge mentale pour comprendre l’indirection, et il est très clair quels champs de la bar vous définissez explicitement en ignorant (en toute sécurité) ceux que vous ne définissez pas.

Vous pouvez changer votre valeur spéciale secrète à 0 et exploiter la sémantique du membre de structure par défaut de C

 struct foo bar = { .id = 42, .current_route = new_route }; update(&bar); 

passera alors 0 en tant que membres de la barre non spécifiés dans l’initialiseur.

Ou vous pouvez créer une macro qui effectuera l’initialisation par défaut pour vous:

 #define FOO_INIT(...) { .id = -1, .current_route = -1, .quux = -1, ## __VA_ARGS__ } struct foo bar = FOO_INIT( .id = 42, .current_route = new_route ); update(&bar); 

vous permet de définir des fonctions variadiques (qui acceptent un nombre indéfini d’arguments, comme printf() ). Je définirais une fonction qui prend un nombre arbitraire de paires d’arguments, l’une qui spécifie la propriété à mettre à jour et l’autre qui spécifie la valeur. Utilisez une enum ou une chaîne pour spécifier le nom de la propriété.

Peut-être envisager d’utiliser une définition de macro de préprocesseur à la place:

 #define UPDATE_ID(instance, id) ({ (instance)->id= (id); }) #define UPDATE_ROUTE(instance, route) ({ (instance)->route = (route); }) #define UPDATE_BACKUP_ROUTE(instance, route) ({ (instance)->backup_route = (route); }) #define UPDATE_CURRENT_ROUTE(instance, route) ({ (instance)->current_route = (route); }) 

Si votre instance de (struct foo) est globale, vous n’avez bien sûr pas besoin du paramètre pour cela. Mais je suppose que vous avez probablement plus d’une instance. L’utilisation du bloc ({…}) est un is GNU qui s’applique à GCC; c’est un moyen agréable (sûr) de garder des lignes en bloc. Si, par la suite, vous devez append d’autres éléments aux macros, comme la vérification de la validation de la plage, vous n’aurez plus à vous soucier de casser des choses comme les instructions if / else, etc.

C’est ce que je ferais, en fonction des exigences que vous avez indiquées. Des situations comme celle-ci sont l’une des raisons pour lesquelles j’ai commencé à utiliser beaucoup Python. gérer les parameters par défaut devient beaucoup plus simple qu’avec C. (Je suppose que c’est une prise python, désolé 😉

Un modèle utilisé par gobject est une fonction variadique et des valeurs énumérées pour chaque propriété. L’interface ressemble à quelque chose comme:

 update (ID, 1, BACKUP_ROUTE, 4, -1); /* -1 terminates the parameter list */ 

Ecrire une fonction varargs est facile – voir http://www.eskimo.com/~scs/cclass/int/sx11b.html . Faites simplement correspondre les paires clé -> valeur et définissez les atsortingbuts de structure appropriés.

Comme il semble que vous ayez seulement besoin de cette structure pour la fonction update() , n’utilisez pas de structure pour cela, elle ne fera que masquer votre intention derrière cette construction. Vous devriez peut-être repenser la raison pour laquelle vous modifiez et mettez à jour ces champs et définissez des fonctions ou des macros distinctes pour ces “petites” modifications.

par exemple

 #define set_current_route(id, route) update(id, dont_care, dont_care, route) #define set_route(id, route) update(id, dont_care, route, dont_care) #define set_backup_route(id, route) update(id, route, dont_care, dont_care) 

Ou encore mieux écrire une fonction pour chaque changement. Comme vous l’avez déjà remarqué, vous ne modifiez pas toutes les propriétés en même temps, vous pouvez donc modifier une seule propriété à la fois. Cela n’améliore pas seulement la lisibilité, mais vous aide également à gérer les différents cas, par exemple, vous n’avez pas besoin de vérifier tous les “dont_care” car vous savez que seul l’itinéraire actuel est modifié.

Que diriez-vous de quelque chose comme:

 struct foo bar; update(init_id(42, init_dont_care(&bar))); 

avec:

 struct foo* init_dont_care(struct foo* bar) { bar->id = dont_care; bar->route = dont_care; bar->backup_route = dont_care; bar->current_route = dont_care; return bar; } 

et:

 struct foo* init_id(int id, struct foo* bar) { bar->id = id; return bar; } 

et en conséquence:

 struct foo* init_route(int route, struct foo* bar); struct foo* init_backup_route(int backup_route, struct foo* bar); struct foo* init_current_route(int current_route, struct foo* bar); 

En C ++, un motif similaire porte un nom dont je ne me souviens plus tout à l’heure.

EDIT : Il s’appelle l’ idiome de paramètre nommé .

Je suis rouillé avec les structures, donc il me manque probablement quelques mots-clés ici. Mais pourquoi ne pas commencer avec une structure globale avec les valeurs par défaut initialisées, la copier dans votre variable locale, puis la modifier?

Un initialiseur comme:

 void init_struct( structType * s ) { memcopy(s,&defaultValues,sizeof(structType)); } 

Ensuite, quand vous voulez l’utiliser:

 structType foo; init_struct( &foo ); // get defaults foo.fieldICareAbout = 1; // modify fields update( &foo ); // pass to function 

Vous pouvez résoudre le problème avec une macro X

Vous changeriez votre définition de structure en:

 #define LIST_OF_foo_MEMBERS \ X(int,id) \ X(int,route) \ X(int,backup_route) \ X(int,current_route) #define X(type,name) type name; struct foo { LIST_OF_foo_MEMBERS }; #undef X 

Et vous pourrez alors facilement définir une fonction flexible qui définit tous les champs sur dont_care .

 #define X(type,name) in->name = dont_care; void setFooToDontCare(struct foo* in) { LIST_OF_foo_MEMBERS } #undef X 

Après la discussion ici , on pourrait aussi définir une valeur par défaut de cette manière:

 #define X(name) dont_care, const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo }; #undef X 

Ce qui se traduit par:

 const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,}; 

Et utilisez-le comme dans la réponse hlovdal , avec l’avantage qu’ici la maintenance est plus facile, c.-à- foo_DONT_CARE . foo_DONT_CARE changer le nombre de membres struct mettra automatiquement à jour foo_DONT_CARE . Notez que la dernière virgule “fausse” est acceptable .

J’ai d’abord appris le concept de X-Macros lorsque je devais résoudre ce problème .

Il est extrêmement flexible pour les nouveaux champs ajoutés à la structure. Si vous avez différents types de données, vous pouvez définir différentes valeurs dont_care fonction du type de données: à partir de là , vous pouvez vous inspirer de la fonction utilisée pour imprimer les valeurs du deuxième exemple.

Si vous êtes d’accord avec une structure all int , vous pouvez omettre le type de données de LIST_OF_foo_MEMBERS et simplement changer la fonction X de la définition de la structure en #define X(name) int name;

La manière la plus élégante serait de mettre à jour directement les champs de la structure, sans avoir à utiliser la fonction update() , mais peut-être qu’il ya de bonnes raisons de l’utiliser sans la question.

 struct foo* bar = get_foo_ptr(); foo_ref.id = 42; foo_ref.current_route = new_route; 

Ou, comme Pukku l’a suggéré, vous pouvez créer des fonctions d’access distinctes pour chaque champ de la structure.

Sinon, la meilleure solution que je puisse imaginer consiste à traiter une valeur de ‘0’ dans un champ struct comme un indicateur ‘ne pas mettre à jour’. Vous devez donc créer une fonction pour renvoyer une structure à zéro, puis l’utiliser pour mettre à jour.

 struct foo empty_foo(void) { struct foo bar; bzero(&bar, sizeof (struct bar)); return bar; } struct foo bar = empty_foo(); bar.id=42; bar.current_route = new_route; update(&bar); 

Cependant, cela peut ne pas être très réalisable si 0 est une valeur valide pour les champs de la structure.