Opaque C structs: comment doivent-elles être déclarées?
J'ai vu les deux de la les deux styles de déclarer opaque types en C Api. Est-il un avantage évident à l'aide d'un style plus qu'un autre?
Option 1
//foo.h
typedef struct foo * fooRef;
void doStuff(fooRef f);
//foo.c
struct foo {
int x;
int y;
};
Option 2
//foo.h
typedef struct _foo foo;
void doStuff(foo *f);
//foo.c
struct _foo {
int x;
int y;
};
- Voir aussi Est-ce une bonne idée de la définition de type des pointeurs?
- Notez également que les noms commençant par un trait de soulignement sont pas une bonne idée dans le code de l'utilisateur (par opposition au système de code — la mise en œuvre). §7.1.3 "Réservés identifiants" de la norme: • Tous les identificateurs commençant par un trait de soulignement et une lettre majuscule ou un autre trait de soulignement sont toujours réservés pour toute utilisation. • Tous les identificateurs commençant par un caractère de soulignement sont toujours réservés pour une utilisation comme identificateurs de fichier portée à la fois l'ordinaire et le nom de la balise espaces.
- Opaque exemple
- (Un peu en retard à la fête, je sais, mais) j'ai juste proposé un exemple complet que
Option 1.5
, ici: stackoverflow.com/a/54488289/4561887.
Vous devez vous connecter pour publier un commentaire.
Mon vote est pour la troisième option qui mouviciel posté puis supprimé:
Si vous ne pouvez vraiment pas se tenir en tapant le
struct
mot-clé,typedef struct foo foo;
(note: se débarrasser de l'inutile et de la problématique de soulignement) est acceptable. Mais quoi que vous fassiez, jamais utilisationtypedef
à définir des noms pour les types de pointeur. Il masque l'extrême importance de l'information que les variables de ce type de référence d'un objet qui pouvait être modifié chaque fois que vous passez à des fonctions, et il le fait de traiter différemment qualifiés (par exemple,const
qualifié) versions de l'aiguille d'une grande douleur.const
dans la définition de type est si vous avez une situation où vous voulez exprimer que certaines fonctions de l'API de modifier l'objet, et d'autres n'ont pas. Vous pourriez avoirtypedef struct foo *foo; typedef struct const foo *const_foo;
, mais c'est triste!const
est un détail d'implémentation, la dernière où vous avez besoin d'un niveau supplémentaire d'indirection dans le cas de compactage; l'api peut très bien être agnostique à ces détails, mais seulement si opaque types sont utilisés pour masquer les qualificatifs/pointeursconst
qualificatif est pas valide pour les cordes immuables (ou tout objet alloué) parce que votre mise en œuvre de l'objet ne peut pasfree
unconst
qualifiés pointeur (free
prend un non-const
qualifiésvoid *
, pour de bonnes raisons). Ce n'est pas une simple question technique, mais une question de violation de la sémantique deconst
. Bien sûr, vous pouvez lancer leconst
loin dans votreimmutable_string_free
fonction, mais maintenant nous allons entrer dans le territoire de sale hacks. Tous un objet opaque fonction d'allocation doit toujours retournerfootype *
, et la fonction de libre, doit prendrefootype *
.const
est généralement pas recommandé et est - ce que je sais - en fait, ce ne sont pas mentionnés dans la norme C comme une possible conversion de types pointeur (vous ne pouvez aller à partir de non-qualifié qualifié); cependant, les objets alloués qui ne sont jamais modifiés au cours de leur vie après l'initialisation sont assez communs, et je ne vois pas vraiment l'avantage de garder un non-qualifiés version du pointeur dans le seul but de libérer l'objet; casting pour non-const
peut être un hack, mais je ne sais pas de toute autre solution de contournement possibleconst
-version qualifiée de le pointeur doit être utilisée dans la valeur de retour de la fonction d'allocation(s) et l'argument de la fonction de libération(s). Toutes les autres fonctions doivent prendreconst
qualifié de pointeurs. C'est une des raisons pourquoi faireconst
(et le pointeur) un construit-dans le cadre de la définition de type est une mauvaise idée.const
est supprimé à partir du paramètre réel, donc ni "const pointeur de la magie", ni de "const " magie" de restreindre la bibliothèque de quelque façon que ce soit. Et si c'est un "pointeur const magique" ou un "pointeur non-const de la magie" est un détail d'implémentation... ce n'est pas important à l'appelant de code dans le moins, parce qu'il n'est pas censé toucher à la magie, ne doit même pas déréférencer le pointeur qui est une première étape nécessaire de toucher à la magie._foo
même lors de la déclaration detypedef _foo struct {} foo;
en C.bar(const fooRef)
déclare immuable adresse en argument.bar(const foo *)
déclare une adresse d'un immuable foo comme argument.Pour cette raison, j'ai tendance à préférer l'option 2. I. e., le type d'interface est celle où cv-ness peuvent être spécifiés à chaque niveau d'indirection. Bien sûr, on peut pas de côté l'option 1 de la bibliothèque de l'écrivain et il suffit d'utiliser
foo
, à vous ouvrir toutes sortes d'horreur lors de la bibliothèque de l'écrivain changements de la mise en œuvre. (I. e., l'option 1 de la bibliothèque de l'écrivain ne perçoit quefooRef
fait partie de l'invariant de l'interface et quefoo
peut venir, aller, être modifiée, que ce soit. L'option 2 de la bibliothèque de l'écrivain perçoit quefoo
fait partie de l'invariant de l'interface.)De plus je suis surpris que personne n'a suggéré combiné typedef/struct constructions.
typedef struct { ... } foo;
foo
partie de l'interface. C'est tout l'intérêt de faire les choses de cette façon.Option de 1,5
Je suis habitué à utiliser Option 1, sauf votre nom, votre référence avec
_h
pour indiquer qu'il s'agit d'une "poignée" d'un style C "objet" de cette manière, C "classe". Ensuite, vous assurer que vos prototypes de fonction utilisationconst
où le contenu de cet objet "poignée" est une entrée uniquement, et ne peut pas être modifié, et ne pas utiliserconst
où le contenu peut être changé.Voici un exemple complet:
La seule amélioration au-delà de ce qui serait d':
void
.Ajouter une configuration struct appelé
my_module_config_t
à l' .h de fichier, et de le transmettre à laopen
fonction de mise à jour des variables internes lorsque vous créez un nouvel objet. Exemple: