Valeurs par défaut dans une structure C


92

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

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

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

  mise à jour (42, dont_care, dont_care, nouvelle_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 pense plutôt lui passer une structure, mais remplir au préalable la structure avec 'dont_care' est encore plus fastidieuse 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 ne se soucient pas et définir simplement les champs qui m'intéressent après l'avoir déclarée comme variable locale?

    struct toto 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 reste soit par défaut à -1 (le code secret pour 'ne se soucie pas')

Réponses:


155

Bien que les macros et / ou fonctions (comme déjà suggéré) fonctionnent (et peuvent avoir d'autres effets positifs (par exemple, des hooks de débogage)), elles sont plus complexes que nécessaire. La solution la plus simple et probablement la plus élégante est de définir simplement 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 aucune surcharge mentale pour comprendre l'indirection, et il est très clair quels champs barvous définissez explicitement tout en ignorant (en toute sécurité) ceux que vous ne définissez pas.


6
Un autre avantage de cette approche est qu'elle ne repose pas sur les fonctionnalités d'un C99 pour fonctionner.
D.Shawley

24
Quand je suis passé à ces 500 lignes "tombées" du projet. J'aimerais pouvoir voter deux fois sur celui-ci!
Arthur Ulfeldt

4
PTHREAD_MUTEX_INITIALIZERl'utilise également.
yingted

J'ai ajouté une réponse ci-dessous en s'appuyant sur X-Macros pour être flexible sur le nombre d'éléments présents dans la structure.
Antonio le

15

Vous pouvez changer la valeur spéciale de votre secret à 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 alors 0 comme membres de bar 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);

FOO_INIT fonctionne-t-il? Le compilateur devrait se plaindre, je pense, si vous initialisez le même membre deux fois.
Jonathan Leffler

1
Je l'ai essayé avec gcc et cela ne s'est pas plaint. De plus, je n'ai rien trouvé contre cela dans la norme, en fait, il y a un exemple où l'écrasement est spécifiquement mentionné.
jpalecek

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: L'initialisation doit avoir lieu dans l'ordre de la liste des initialiseurs, chaque initialiseur fourni pour un sous-objet particulier remplaçant tout initialiseur précédemment répertorié pour le même sous-objet;
altendky

2
Si cela est vrai, ne pourriez-vous pas définir DEFAULT_FOO, qui a tous les initialiseurs, puis faire struct foo bar = {DEFAULT_FOO, .id = 10}; ?
John

7

<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 prendrait un nombre arbitraire de paires d'arguments, une qui spécifie la propriété à mettre à jour et une qui spécifie la valeur. Utilisez une enumchaîne ou une chaîne pour spécifier le nom de la propriété.


Salut @Matt, comment mapper des enumvariables sur le structterrain? Le coder en dur? Ensuite, cette fonction ne sera applicable qu'à des fichiers spécifiques struct.
misssprite

5

Envisagez peut-être 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, alors vous n'avez pas besoin du paramètre pour cela bien sûr. Mais je suppose que vous avez probablement plus d'une instance. L'utilisation du bloc ({...}) est un GNU-ism qui s'applique à GCC; c'est un moyen agréable (sûr) de garder les lignes ensemble comme un bloc. Si vous devez par la suite ajouter plus aux macros, comme la vérification de la validation de plage, vous n'aurez pas à vous soucier de casser des choses comme les instructions if / else et ainsi de suite.

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 beaucoup commencé à utiliser python; gérer les paramètres par défaut et cela devient beaucoup plus simple que jamais avec C. (je suppose que c'est un plug python, désolé ;-)


4

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

L'écriture d'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 attributs de structure appropriés.


4

Comme il semble que vous n'avez besoin que de cette structure pour la update()fonction, n'utilisez pas du tout de structure pour cela, cela ne fera qu'obscurcir votre intention derrière cette construction. Vous devriez peut-être repenser pourquoi vous modifiez et mettez à jour ces champs et définir des fonctions ou des macros distinctes pour ces "petits" changements.

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 changement. Comme vous l'avez déjà remarqué, vous ne modifiez pas toutes les propriétés en même temps, il est donc possible de ne modifier qu'une seule 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 seule la route actuelle est modifiée.


certains cas en changeront 3 dans le même appel.
Arthur Ulfeldt

Vous pouvez avoir cette séquence: set_current_rout (id, route); set_route (id, route); apply_change (id); ou similaire. Ce que je pense, c'est que vous pouvez avoir un appel de fonction pour chaque passeur. Et faites le vrai calcul (ou quoi que ce soit) plus tard.
quinmars

4

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 pour le moment.

EDIT : C'est ce qu'on appelle l' idiome du paramètre nommé .


Je crois que c'est appelé un constructeur.
Inconnu le

Non, je voulais dire ce que vous pouvez faire: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen le

Cela semble être appelé "initialisation de type Smalltalk enchaîné", du moins ici: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal
...

2

Je suis rouillé avec les structures, donc il me manque probablement quelques mots-clés ici. Mais pourquoi ne pas commencer par 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, lorsque vous souhaitez l'utiliser:

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

2

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

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 puis vous seriez 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

Suite à la discussion ici , on pourrait également 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'est-à-dire que la modification du nombre de membres de la structure sera automatiquement mise à jourfoo_DONT_CARE . Notez que la dernière virgule "fausse" est acceptable .

J'ai appris le concept des X-Macros pour la première fois lorsque j'ai dû résoudre ce problème .

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

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


Cette technique exploite le fait que le préprocesseur C utilise une liaison tardive et cela peut être très utile lorsque vous voulez qu'un seul endroit définisse quelque chose (par exemple des états) et que vous soyez ensuite sûr à 100% que partout où les éléments utilisés sont toujours dans le même ordre, les nouveaux éléments sont automatiquement ajoutés et les éléments supprimés sont supprimés. Je n'aime pas trop utiliser des noms courts comme Xet généralement ajouter _ENTRYau nom de la liste, par exemple #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal

1

Le moyen le plus élégant serait de mettre à jour les champs de structure directement, sans avoir à utiliser la update()fonction - mais peut-être qu'il y a de bonnes raisons de 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;

Ou vous pouvez, comme Pukku l'a suggéré, créer des fonctions d'accès séparées pour chaque champ de la structure.

Sinon, la meilleure solution à laquelle je puisse penser est de traiter une valeur de `` 0 '' dans un champ struct comme un indicateur `` ne pas mettre à jour '' - il vous suffit donc de créer une fonction pour renvoyer une structure remise à zéro, puis de 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 faisable, si 0 est une valeur valide pour les champs de la structure.


Un réseau est une bonne raison =) Mais si -1 est la valeur «ne se soucie pas», alors restructurez simplement la fonction empty_foo (), et utilisez cette approche?
gnud le

désolé, j'ai cliqué sur le vote négatif par erreur et je ne l'ai remarqué que plus tard !!! Je ne trouve aucun moyen d'annuler cela maintenant à moins qu'il n'y ait une modification ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.