Cacher les membres dans une structure C

J’ai lu sur la POO dans C, mais je n’ai jamais aimé comment vous ne pouvez pas avoir de membres de données privés comme vous pouvez le faire en C ++. Mais alors, j’ai pensé que vous pouviez créer 2 structures. L’un est défini dans le fichier d’en-tête et l’autre est défini dans le fichier source.

// ========================================= // in somestruct.h typedef struct { int _public_member; } SomeStruct; // ========================================= // in somestruct.c #include "somestruct.h" typedef struct { int _public_member; int _private_member; } SomeStructSource; SomeStruct *SomeStruct_Create() { SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource)); p->_private_member = 42; return (SomeStruct *)p; } 

De là, vous pouvez simplement lancer une structure à l’autre. Est-ce considéré comme une mauvaise pratique? Ou est-ce fait souvent?

Personnellement, j’aimerais plus:

 typedef struct { int _public_member; /*I know you wont listen, but don't ever touch this member.*/ int _private_member; } SomeStructSource; 

C, après tout, si les gens veulent bousiller, ils devraient pouvoir le faire – pas besoin de cacher des choses, sauf:

Si ce dont vous avez besoin est de conserver la compatibilité ABI / API, il existe deux approches plus courantes.

  • Ne donnez pas à vos clients un access à la structure, donnez-leur un handle opaque (un vide avec un joli nom), fournissez des fonctions init / destroy et accesseur pour tout. Cela vous permet de modifier la structure sans même recomstackr les clients si vous écrivez une bibliothèque.

  • fournissez un handle opaque dans le cadre de votre structure, que vous pouvez allouer comme vous le souhaitez. Cette approche est même utilisée en C ++ pour assurer la compatibilité ABI.

par exemple

  struct SomeStruct { int member; void* internals; //allocate this to your private struct }; 

sizeof(SomeStruct) != sizeof(SomeStructSource) . Cela amènera quelqu’un à te trouver et à te tuer un jour.

Vous l’avez presque, mais vous n’êtes pas allé assez loin.

Dans l’en-tête:

 struct SomeStruct; typedef struct SomeStruct *SomeThing; SomeThing create_some_thing(); destroy_some_thing(SomeThing thing); int get_public_member_some_thing(SomeThing thing); void set_public_member_some_thing(SomeThing thing, int value); 

Dans le .c:

 struct SomeStruct { int public_member; int private_member; }; SomeThing create_some_thing() { SomeThing thing = malloc(sizeof(*thing)); thing->public_member = 0; thing->private_member = 0; return thing; } ... etc ... 

Le fait est que, maintenant, les consommateurs ne connaissent pas les composants internes de SomeStruct, et vous pouvez le changer en toute impunité, en ajoutant et en supprimant des membres à volonté, même sans que les consommateurs aient besoin de recomstackr. Ils ne peuvent pas non plus “mordre” des membres directement ou allouer SomeStruct à la stack. Cela peut évidemment être considéré comme un inconvénient.

Je ne recommande pas d’utiliser le pattern struct public. Le modèle de conception correct, pour la POO dans C, consiste à fournir des fonctions permettant d’accéder à toutes les données, sans jamais autoriser l’access public aux données. Les données de classe doivent être déclarées à la source, afin d’être privées, et être référencées de manière directe, où Create et Destroy atsortingbuent et libèrent les données. Ainsi, le dilemme public / privé n’existera plus.

 /*********** header.h ***********/ typedef struct sModuleData module_t' module_t *Module_Create(); void Module_Destroy(module_t *); /* Only getters and Setters to access data */ void Module_SetSomething(module_t *); void Module_GetSomething(module_t *); /*********** source.c ***********/ struct sModuleData { /* private data */ }; module_t *Module_Create() { module_t *inst = (module_t *)malloc(sizeof(struct sModuleData)); /* ... */ return inst; } void Module_Destroy(module_t *inst) { /* ... */ free(inst); } /* Other functions implementation */ 

De l’autre côté, si vous ne souhaitez pas utiliser Malloc / Free (ce qui peut être inutile dans certaines situations), je vous suggère de masquer la structure dans un fichier privé. Les membres privés seront accessibles, mais cela sur le pieu de l’utilisateur.

 /*********** privateTypes.h ***********/ /* All private, non forward, datatypes goes here */ struct sModuleData { /* private data */ }; /*********** header.h ***********/ #include "privateTypes.h" typedef struct sModuleData module_t; void Module_Init(module_t *); void Module_Deinit(module_t *); /* Only getters and Setters to access data */ void Module_SetSomething(module_t *); void Module_GetSomething(module_t *); /*********** source.c ***********/ void Module_Init(module_t *inst) { /* perform initialization on the instance */ } void Module_Deinit(module_t *inst) { /* perform deinitialization on the instance */ } /*********** main.c ***********/ int main() { module_t mod_instance; module_Init(&mod_instance); /* and so on */ } 

Ne fais jamais cela. Si votre API prend en charge quelque chose qui prend SomeStruct en tant que paramètre (ce à quoi je l’attends), ils pourraient en allouer un sur une stack et le transmettre. Vous obtiendriez des erreurs majeures en essayant d’accéder au membre privé depuis celui du compilateur. allocates pour la classe client ne contient pas d’espace pour cela.

La manière classique de masquer les membres dans une structure consiste à en faire un vide *. Il s’agit essentiellement d’un handle / cookie que seuls vos fichiers d’implémentation connaissent. Presque toutes les bibliothèques C le font pour les données privées.

Quelque chose de similaire à la méthode que vous avez proposée est en effet parfois utilisé (par exemple, voir les différentes variétés de struct sockaddr* dans l’API des sockets BSD), mais il est presque impossible de l’utiliser sans violer les règles ssortingctes d’alias de C99.

Vous pouvez cependant le faire en toute sécurité :

somestruct.h :

 struct SomeStructPrivate; /* Opaque type */ typedef struct { int _public_member; struct SomeStructPrivate *private; } SomeStruct; 

somestruct.c :

 #include "somestruct.h" struct SomeStructPrivate { int _member; }; SomeStruct *SomeStruct_Create() { SomeStruct *p = malloc(sizeof *p); p->private = malloc(sizeof *p->private); p->private->_member = 0xWHATEVER; return p; } 

J’écrirais une structure cachée et la référencerais en utilisant un pointeur dans la structure publique. Par exemple, votre .h pourrait avoir:

 typedef struct { int a, b; void *private; } public_t; 

Et votre .c:

 typedef struct { int c, d; } private_t; 

Cela ne protège évidemment pas de l’arithmétique des pointeurs, et ajoute un peu de surcharge pour l’allocation / la désallocation, mais je suppose que cela dépasse le cadre de la question.

Utilisez la solution de contournement suivante:

 #include  #define C_PRIVATE(T) struct T##private { #define C_PRIVATE_END } private; #define C_PRIV(x) ((x).private) #define C_PRIV_REF(x) (&(x)->private) struct T { int a; C_PRIVATE(T) int x; C_PRIVATE_END }; int main() { struct T t; struct T *tref = &t; ta = 1; C_PRIV(t).x = 2; printf("ta = %d\nt.x = %d\n", ta, C_PRIV(t).x); tref->a = 3; C_PRIV_REF(tref)->x = 4; printf("tref->a = %d\ntref->x = %d\n", tref->a, C_PRIV_REF(tref)->x); return 0; } 

Le résultat est:

 ta = 1 tx = 2 tref->a = 3 tref->x = 4 

Il existe de meilleures façons de le faire, comme utiliser un pointeur void * sur une structure privée de la structure publique. Comme vous le faites, vous trompez le compilateur.

Cette approche est valide, utile, standard C.

Une approche légèrement différente, utilisée par l’API Sockets, définie par BSD Unix, est le style utilisé pour struct sockaddr .

Pas très privé, étant donné que le code appelant peut être (SomeStructSource *) vers un (SomeStructSource *) . En outre, que se passe-t-il lorsque vous souhaitez append un autre membre du public? Vous devrez briser la compatibilité binary.

EDIT: il me manquait que c’était dans un fichier .c, mais il n’y a vraiment rien qui empêche un client de le copier, ou même d’ #include le fichier .c directement.

Relié, mais pas exactement caché.

Est-ce de désapprouver conditionnellement les membres.

Notez que cela fonctionne pour GCC / Clang, mais MSVC et d’autres compilateurs peuvent aussi se déconseiller, il est donc possible de proposer une version plus portable.

Si vous créez avec des avertissements ou des avertissements assez ssortingcts, cela évite au moins une utilisation accidentelle.

 // ========================================= // in somestruct.h #ifdef _IS_SOMESTRUCT_C # if defined(__GNUC__) # define HIDE_MEMBER __atsortingbute__((deprecated)) # else # define HIDE_MEMBER /* no hiding! */ # endif #else # define HIDE_MEMBER #endif typedef struct { int _public_member; int _private_member HIDE_MEMBER; } SomeStruct; #undef HIDE_MEMBER // ========================================= // in somestruct.c #define _IS_SOMESTRUCT_C #include "somestruct.h" SomeStruct *SomeStruct_Create() { SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource)); p->_private_member = 42; return (SomeStruct *)p; } 

Ma solution serait de ne fournir que le prototype de la structure interne, puis de déclarer la définition dans le fichier .c. Très utile pour afficher l’interface C et utiliser C ++ derrière.

.h:

 struct internal; struct foo { int public_field; struct internal *_internal; }; 

.c:

 struct internal { int private_field; // could be a C++ class }; 

Remarque: Dans ce cas, la variable doit être un pointeur car le compilateur ne peut pas connaître la taille de la structure interne.