J'ai lu quelque chose à propos de OOP en C mais je n'ai jamais aimé le fait que vous ne puissiez pas avoir de données privées comme les membres en C++. Mais ensuite, je me suis dit que vous pouviez créer 2 structures. L'un est défini dans le fichier d'en-tête et l'autre 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;
}
À partir de là, vous pouvez simplement lancer une structure sur une autre ..___ Est-ce considéré comme une mauvaise pratique? Ou est-ce fait souvent?
Personnellement, je préférerais ceci:
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 bousiller, ils devraient être autorisés à - pas besoin de cacher des choses, sauf:
Si ce dont vous avez besoin est de garder la compatibilité ABI/API, il existe 2 approches qui sont plus courantes que celles que j'ai déjà vues.
Ne donnez pas à vos clients l'accès à la structure, donnez-leur un descripteur opaque (un vide * avec un joli nom), fournissez les fonctions init/destroy et accessor pour tout. Cela garantit que vous pouvez modifier la structure sans même recompiler les clients si vous écrivez une bibliothèque.
fournissez une poignée opaque dans votre structure, que vous pouvez allouer comme bon vous semble. 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)
. Ceci va faire que quelqu'un vous trouve et vous assassine 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 qu'ici, les consommateurs ont non / une connaissance des composants 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 se recompiler. Ils ne peuvent pas non plus "accidentellement" directement s'adresser aux membres ou allouer SomeStruct à la pile. Cela peut bien sûr aussi être considéré comme un inconvénient.
Je ne recommande pas d'utiliser le modèle struct public. Le modèle de conception correct, pour OOP en C, consiste à fournir des fonctions pour accéder à toutes les données, ne permettant jamais un accès public à celles-ci. 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 avancée, où Create
et Destroy
effectuent l'allocation et sont libres 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 */
De l'autre côté, si vous ne voulez pas utiliser Malloc/Free (ce qui peut être une surcharge inutile dans certaines situations), je vous suggère de masquer la structure dans un fichier privé. Les membres privés seront accessibles, mais c'est sur la participation 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 tout ce qui prend SomeStruct en tant que paramètre (ce que je suppose, alors ils pourraient en allouer un sur une pile et le transmettre. Vous obtiendrez des erreurs majeures en essayant d'accéder au membre privé depuis celui du compilateur allocates car la classe client ne contient pas d’espace.
La manière classique de masquer des membres dans une structure est d'en faire un vide *. Il s’agit essentiellement d’un cookie/descripteur que seuls vos fichiers d’implémentation connaissent. Presque toutes les bibliothèques C le font pour des données privées.
On utilise parfois quelque chose de similaire à la méthode que vous avez proposée (par exemple, voir les différentes variantes de struct sockaddr*
dans l'API de sockets BSD), mais il est presque impossible de l'utiliser sans enfreindre les règles strictes d'aliasing 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 contre l'arithmétique de pointeur, et ajoute un peu de surcoût pour l'allocation/désallocation, mais je suppose que cela dépasse le cadre 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;
}
Le résultat est:
t.a = 1
t.x = 2
tref->a = 3
tref->x = 4
Il existe de meilleurs moyens de procéder, comme utiliser un pointeur void *
vers une structure privée dans la structure publique. La façon dont vous le faites tromper 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 reconvertir en un (SomeStructSource *)
. De plus, que se passe-t-il lorsque vous souhaitez ajouter un autre membre public? Vous devrez rompre la compatibilité binaire.
EDIT: Il me manquait que c’était dans un fichier .c, mais rien n’empêche vraiment un client de le copier, voire même de #include
directement le fichier .c.
Relatif, mais pas exactement cacher.
Est de déprécier conditionnellement les membres.
Notez que cela fonctionne pour GCC/Clang, mais MSVC et d’autres compilateurs peuvent également devenir obsolètes, donc il est donc possible de créer une version plus portable.
Si vous construisez avec des avertissements assez stricts, ou des avertissements en tant qu'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 ne fournir que le prototype de la structure interne, puis de déclarer la définition dans le fichier .c. 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
};
Remarque: Dans ce cas, la variable doit être un pointeur car le compilateur est incapable de connaître la taille de la structure interne.