web-dev-qa-db-fra.com

Valeurs par défaut dans une structure en 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 () qui est utilisée pour demander des modifications.

 update (42, dont_care, dont_care, new_route); 

c'est vraiment long et si j'ajoute quelque chose à la structure, je dois ajouter un 'dont_care' à CHAQUE appel pour mettre à jour (...).

Je songe plutôt à lui passer un struct, mais le remplir à l'avance avec 'dont_care' est encore plus fastidieux que de l'épeler dans l'appel de fonction. Puis-je créer la structure quelque part avec les valeurs par défaut de ne pas se soucier et simplement définir les champs qui me tiennent à cœur après que je la déclare comme une variable locale?

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

Quel est le moyen le plus élégant de ne transmettre que les informations que je souhaite exprimer à la fonction de mise à jour?

et je veux que tout le reste par défaut soit -1 (le code secret pour 'ne vous en faites pas')

90
Arthur Ulfeldt

Bien que les macros et/ou les fonctions (comme cela a déjà été suggéré) fonctionnent (et puissent avoir d'autres effets positifs (par exemple, des crochets de débogage)), elles sont plus complexes que nécessaire. La solution la plus simple et peut-être la plus élégante consiste simplement à définir une constante que vous utilisez pour l'initialisation de variable:

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’entraîne pratiquement aucune surcharge mentale en ce qui concerne la compréhension de l’indirection indirecte, et il est très clair quels champs de bar vous définissez explicitement tout en ignorant (en toute sécurité) ceux que vous ne définissez pas.

152
hlovdal

Vous pouvez changer votre valeur spéciale secrète en 0 et exploiter la sémantique par défaut des membres de la structure de C

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

passera ensuite 0 en tant que membre de barre non spécifié 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);
15
jpalecek

<stdarg.h> 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, une qui spécifie la propriété à mettre à jour et une autre qui spécifie la valeur. Utilisez un enum ou une chaîne pour spécifier le nom de la propriété.

7
Matt J

Peut-être envisagez-vous 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 évidemment pas besoin du paramètre pour cela. Mais je suppose que vous avez probablement plus d'une instance. Utiliser le bloc ({...}) est un GNU-isme qui s’applique à GCC; c'est un bon moyen (sûr) de garder les lignes ensemble en bloc. Si vous devez ultérieurement ajouter d'autres macros, telles que la vérification de validation de plage, vous n'aurez pas à craindre de casser des choses telles que 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 python beaucoup; la gestion des paramètres par défaut et cela devient beaucoup plus simple que jamais avec C. (Je suppose que c’est un python plug, désolé ;-)

5
digijock

Comme il semble que vous n’ayez besoin de cette structure que pour la fonction update(), n’utilisez pas de structure pour cela, cela obscurcira simplement 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 mieux encore, écrivez une fonction pour chaque cas de modification. Comme vous avez déjà remarqué que vous ne modifiez pas toutes les propriétés en même temps, il est donc possible de ne modifier qu'une propriété à la fois. Cela améliore non seulement la lisibilité, mais vous aide également à gérer les différents cas, par exemple. vous n'avez pas à vérifier tous les "dont_care" car vous savez que seul l'itinéraire actuel est modifié.

4
quinmars

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 modèle similaire a un nom dont je ne me souviens pas tout à l'heure.

[~ # ~] éditer [~ # ~] : C'est ce qu'on appelle le idiome de paramètre nommé .

4
Reunanen

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 */

Écrire une fonction varargs est facile - voir http://www.eskimo.com/~scs/cclass/int/sx11b.html . Il suffit de faire correspondre les paires clé -> valeur et de définir les attributs de structure appropriés.

4
John Millikin

Vous pouvez résoudre le problème avec un X-Macro

Vous changeriez votre définition de struct 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 seriez alors en mesure de définir facilement 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 également définir une valeur par défaut de la manière suivante:

#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 réponse hlovdal , avec l'avantage que la maintenance est ici plus facile, c'est-à-dire que le nombre de membres de la structure sera mis à jour automatiquement foo_DONT_CARE. Notez que la dernière virgule "parasite" est acceptable .

J'ai d'abord appris le concept de X-Macros quand j'ai dû aborder ce problème .

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

Si vous êtes d'accord avec une structure tout 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;.

2
Antonio

Je suis rouillé avec les structures, alors 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
2
Steven Fisher

La manière la plus élégante serait de mettre à jour les champs de structure directement, sans avoir à utiliser la fonction update() - mais il y a peut-être de bonnes raisons pour l'utiliser qui ne figurent pas dans la question.

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

Vous pouvez également, comme suggéré par Pukku, créer des fonctions d’accès distinctes pour chaque champ de la structure.

Sinon, la meilleure solution à laquelle je puisse penser consiste à traiter la valeur '0' dans un champ struct comme un indicateur 'ne pas mettre à jour' - vous créez donc une fonction pour renvoyer une structure mise à zéro, puis vous l'utilisez 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 pourrait ne pas être très faisable, si 0 est une valeur valide pour les champs de la structure.

1
gnud