69 votes

Cacher des membres dans une structure C

J'ai lu des articles sur la POO en C mais je n'ai jamais aimé le fait qu'on ne puisse pas avoir de membres de données privés comme en C++. Mais il m'est venu à l'esprit que l'on pouvait créer deux structures. L'une est définie dans le fichier d'en-tête et l'autre est définie 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;
}

A partir de là, vous pouvez simplement jeter une structure sur l'autre. Est-ce considéré comme une mauvaise pratique ? Ou est-ce que cela se fait souvent ?

1 votes

Pourquoi faire si compliqué - Si l'outil ne fait pas ce dont vous avez besoin, utilisez-en un autre.

0 votes

Je pense que cela violerait les règles d'aliasing des objets, au moins en C99. Je sais que c'est le cas en C++.

35 votes

Ce serait terrible de fermer ce site ! Pourquoi les gens votent-ils à la baisse sur une question aussi valable ? Parce qu'ils ont oublié comment faire les choses en C ?

52voto

hobbs Points 71946

sizeof(SomeStruct) != sizeof(SomeStructSource) . Ce site se faire en sorte que quelqu'un vous trouve et vous assassine un jour.

22 votes

Et n'importe quel jury les laisserait partir ensuite.

27 votes

"Toujours coder comme si la personne qui finit par maintenir votre code est un psychopathe violent qui sait où vous habitez." (attribué à Rick Osborne)

42voto

nos Points 102226

Personnellement, je serais plutôt comme ça :

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

C'est C après tout, si les gens veulent faire des conneries, ils devraient être autorisés à le faire - pas besoin de cacher des choses, sauf :

Si ce dont vous avez besoin est de garder l'ABI/API compatible, il y a deux approches qui sont plus communes d'après ce que j'ai vu.

  • Ne donnez pas à vos clients l'accès à la structure, donnez-leur un handle opaque (un void* avec un joli nom), fournissez des fonctions init/destroy et accessor pour tout. Cela permet de s'assurer que vous pouvez modifier la structure sans même recompiler les clients si vous écrivez une bibliothèque.

  • fournir un handle opaque comme partie 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
 };

1 votes

Je trouve que c'est une très mauvaise conception de permettre au client d'accéder à n'importe quel membre d'une structure. La structure entière devrait être privée. L'accès à ses membres doit se faire par le biais de getters et setters.

8 votes

@fanl le faire en C a beaucoup d'implications, par exemple pour cacher une structure de cette façon, il devient assez difficile de l'allouer sur la pile, ou en ligne comme un membre d'une autre structure. Le moyen le plus facile de s'en sortir est d'allouer dynamiquement la structure et de n'exposer qu'un void* ou un handle, et bien que cela puisse être acceptable dans certains cas, il y a de nombreux cas où les implications sont trop importantes et vous empêcheront de profiter de ce que le C vous offre.

1 votes

À mon avis, le deuxième exemple donné ici devrait être le site n'oubliez pas de prévoir une fonction destructeur.

27voto

Logan Capaldo Points 22145

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 fichier .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, ici maintenant, les consommateurs ont pas de connaissance des éléments internes de SomeStruct, et vous pouvez le modifier en toute impunité, en ajoutant et en supprimant des membres à volonté, même sans que les consommateurs aient besoin de recompiler. Ils ne peuvent pas non plus "accidentellement" munger des membres directement, ou allouer SomeStruct sur la pile. Bien sûr, cela peut aussi être considéré comme un inconvénient.

18 votes

Certains envisagent d'utiliser typedef pour cacher les pointeurs est une mauvaise idée, en particulier parce qu'il est plus évident que SomeStruct * doit être libéré d'une manière ou d'une autre que SomeThing qui ressemble à une variable de pile ordinaire. En effet, vous pouvez toujours déclarer struct SomeStruct; et, tant que vous ne le définissez pas, les gens seront obligés d'utiliser SomeStruct * sans pouvoir déréférencer leurs membres, ce qui permet d'obtenir le même effet sans cacher le pointeur.

20voto

fanl Points 738

Je ne recommande pas l'utilisation du modèle public struct. Le modèle de conception correct, pour la POO en C, est de fournir des fonctions pour accéder à toutes les données, sans jamais permettre un accès public aux données. Les données de la 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 y Destroy fait l'allocation et la libération des données. De cette manière, 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 */

D'un autre côté, si vous ne voulez pas utiliser Malloc/Free (qui peut être une surcharge inutile dans certaines situations), je vous suggère de cacher la structure dans un fichier privé. Les membres privés seront accessibles, mais c'est à la charge 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 */
}

2 votes

Une difficulté avec cette approche est qu'elle nécessite l'utilisation de malloc/free même dans des situations où il devrait être possible pour la structure d'être simplement créée comme une variable de pile et de disparaître lorsque la méthode sort. L'utilisation de malloc/free pour des choses qui devraient avoir une sémantique d'empilement peut conduire à une fragmentation de la mémoire si, entre la création/destruction, un autre code doit créer des objets persistants. Ce problème peut être atténué si l'on fournit une méthode permettant d'utiliser un bloc de stockage transmis pour contenir l'objet, et si l'on définit un int[] de taille appropriée à cet effet.

0 votes

@supercat Vrai, si vous ne voulez pas utiliser malloc/free dans votre système embarqué, rendez la structure privée pour le programmeur, pas pour le code. J'ai modifié ma réponse pour traiter ce point.

1 votes

C'est une approche raisonnable. Une approche qui renforcerait encore plus l'usage approprié serait de définir une classe typedef int module_store_t[20]; et ensuite avoir un module_t *Module_CreateIn(module_store_t *p) . Le code pourrait créer une variable automatique de type module_store_t et ensuite utiliser Module_CreateIn pour en dériver un pointeur sur un module nouvellement initialisé dont la durée de vie correspondrait à celle de l'auto-variable.

9voto

NG. Points 12989

Ne fais jamais ça. Si votre API supporte tout ce qui prend SomeStruct comme paramètre (ce qui devrait être le cas), ils pourraient en allouer un sur une pile et le passer. Vous obtiendrez des erreurs majeures en essayant d'accéder au membre privé puisque celui que le compilateur alloue pour la classe client ne contient pas d'espace pour lui.

La façon classique de cacher les membres d'une structure est d'en faire un void*. Il s'agit en fait d'un handle/cookie que seuls les fichiers d'implémentation connaissent. Presque toutes les bibliothèques C font cela pour les données privées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X