Cacher des membres dans une structure
j'ai lu sur L'OOP en C mais je n'ai jamais aimé comment vous ne pouvez pas avoir des membres de données privées comme vous pouvez en C++. Mais alors il est venu à mon esprit que vous pourriez créer 2 structures. Celle-ci 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;
}
de là, vous pouvez simplement mouler une structure à l'autre. Est-ce considéré comme une mauvaise pratique? Ou est-il fait souvent?
13 réponses
personnellement, je préfère ç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 foirer, ils devraient être autorisés à - pas besoin de cacher des choses, sauf:
si ce dont vous avez besoin est de maintenir la compatibilité ABI/API, il y a 2 approches qui sont plus courantes d'après ce que j'ai vu.
-
ne donnez pas à vos clients accès à la structure, donnez-leur une poignée opaque (un vide* avec un joli nom), fournir fonctions init / destroy et accessor pour tout. Cela fait en sorte que vous pouvez changer la structure sans même recompiler les clients si vous écrivez une bibliothèque.
-
fournit une poignée opaque dans le cadre de votre structure, que vous pouvez affecter comme vous voulez. Cette approche est même utilisée en C++ pour fournir la compatibilité ABI.
E. g
struct SomeStruct {
int member;
void* internals; //allocate this to your private struct
};
sizeof(SomeStruct) != sizeof(SomeStructSource)
. Ce causera quelqu'un pour vous trouver et vous assassiner un jour.
tu l'as presque, mais tu n'es 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 point est, ici maintenant les consommateurs ont Non connaissance des 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 recompiler. Ils ne peuvent pas non plus "accidentellement" munge membres directement, ou allouer SomeStruct sur la pile. Cela peut évidemment aussi être considéré comme un inconvénient.
Je ne recommande pas l'utilisation de la structure publique. La conception correcte, pour OOP en C, est de fournir des fonctions pour accéder à toutes les données, ne permettant jamais l'accès 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 d'une manière forward, où Create
et Destroy
fait l'attribution et libre des données. De cette façon, 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 */
dans l'autre côté, si vous ne voulez pas utiliser Malloc/Free (qui peut être inutile pour certaines situations), je vous suggère de cacher la structure dans un fichier privé. Les membres privés seront accessibles, mais que l'utilisateur du jeu.
/*********** 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 faites jamais ça. Si votre API supporte tout ce qui prend SomeStruct comme paramètre (ce que je m'attends à ce qu'il fasse), alors ils peuvent en allouer un sur une pile et le passer. Vous obtiendriez des erreurs majeures en essayant d'accéder au membre privé puisque celui que le compilateur affecte pour la classe client ne contient pas d'espace pour cela.
La façon classique de cacher des membres dans une structure est de faire un vide*. Il s'agit essentiellement d'une poignée / cookie que seul votre fichiers de mise en œuvre de connaître. Presque toutes les bibliothèques C font cela pour des données privées.
quelque chose de similaire à la méthode que vous avez proposée est en effet utilisé parfois (par exemple. voir les différentes variantes de struct sockaddr*
dans L'API des sockets BSD), mais c'est presque impossible à utiliser sans violer les règles d'alias strictes 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 je la référencerais en utilisant un pointeur dans la structure publique. Par exemple, votre .h aurait pu:
typedef struct {
int a, b;
void *private;
} public_t;
et votre .c:
typedef struct {
int c, d;
} private_t;
de toute évidence, il ne protège pas contre l'arithmétique pointeur, et ajoute un peu de frais généraux pour l'allocation/deallocation, mais je suppose que c'est au-delà de la portée de la question.
utilisez la solution de contournement suivante:
#include <stdio.h>
#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;
t.a = 1;
C_PRIV(t).x = 2;
printf("t.a = %d\nt.x = %d\n", t.a, 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;
}
résultat:
t.a = 1
t.x = 2
tref->a = 3
tref->x = 4
Cette approche est valable, utile, norme C.
une approche légèrement différente, utilisée par l'API sockets, qui a été définie par BSD Unix, est le style utilisé pour struct sockaddr
.
pas très privé, étant donné que le code d'appel peut renvoyer à un (SomeStructSource *)
. Aussi, ce qui se passe lorsque vous voulez ajouter un autre membre du public? Vous devrez briser la compatibilité binaire.
EDIT: j'ai raté qu'il a été dans un .C, mais il n'y a vraiment rien qui empêche un client de le copier, ou peut-être même #include
le .c fichier directement.
lié, mais pas exactement se cacher.
vise à déprécier conditionnellement les membres.
notez que cela fonctionne pour GCC / Clang, mais MSVC et d'autres compilateurs peuvent être dépréciés aussi, il est donc possible de trouver une version plus portable.
si vous construisez avec des avertissements assez stricts, ou des avertissements comme erreurs, cela évite au moins une utilisation accidentelle.
// =========================================
// in somestruct.h
#ifdef _IS_SOMESTRUCT_C
# if defined(__GNUC__)
# define HIDE_MEMBER __attribute__((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 fournir seulement le prototype de la structure interne et ensuite déclarer la définition dans le .c fichier. Très utile pour montrer 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
};
Note: dans ce cas, la variable doit être un pointeur car le compilateur est incapable de connaître la taille de la structure interne.