2010-09-29 37 views
11

En C++ et Java, les structures de données peuvent avoir private, public et protected. J'aimerais transférer ce concept dans un programme de langue C que j'écris.Structurer le partitionnement en sections privées et publiques?

Existe-t-il des idiomes pour implémenter des pointeurs de fonction privés ou protégés et des champs de données dans un C struct? Je sais que C struct s sont publics, je suis à la recherche d'un idiome pour aider à cacher certains détails de mise en œuvre et forcer les utilisateurs à utiliser l'interface publique.

Note: La langue a été choisie par le magasin, donc je suis coincé la mise en œuvre des concepts orientés objet en C.

Merci.

+0

Le masquage et l'encapsulation de l'information sont antérieurs à l'OOD. –

Répondre

20

Comme vous le savez, vous ne pouvez pas faire cela. Cependant, il existe des idiomes qui permettent un effet similaire. C vous permettra de faire quelque chose de similaire à ce qu'on appelle l'idiome "pimpl" dans la conception orientée objet. Votre structure peut avoir un pointeur opaque vers une autre structure déclarée en avant qui agit comme les données privées de la structure.Les fonctions qui opèrent sur la structure, prenant la place des fonctions membres, peuvent avoir la définition complète du membre privé, et peuvent en faire usage, alors que d'autres parties du code ne peuvent pas. Par exemple:

Dans un en-tête, foo.h:

struct FooPrivate; 

    struct Foo { 
    /* public: */ 
     int x; 
     double y; 
    /* private: */ 
     struct FooPrivate* p; 
    }; 

    extern struct Foo* Foo_Create(); /* "constructor" */ 

    extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */ 

Dans la mise en œuvre, foo.c:

struct FooPrivate { 
    int z; 
    }; 

    struct Foo* Foo_Create() 
    { 
    struct Foo* foo = malloc(sizeof(Foo)); 

    foo->p = malloc(sizeof(FooPrivate)); 

    foo->x = 0; 
    foo->y = 0; 
    foo->p->z = 0; 

    return foo; 
    } 

    void Foo_DoWhatever(struct Foo* foo) 
    { 
     foo->p->z = 4; /* Can access "private" parts of foo */ 
    } 

Dans un programme:

#include "foo.h" 

    int main() 
    { 
     struct Foo* foo = Foo_Create(); 

     foo->x = 100; /* Can access "public" parts of foo */ 
     foo->p->z = 20; /* Error! FooPrivate is not fully declared here! */ 

     Foo_DoWhatever(foo); /* Can call "member" function */ 

     return 0; 
    } 

Notez le besoin d'utiliser une fonction "constructeur" afin d'allouer de la mémoire pour les données privées. Évidemment, vous devrez coupler cela avec une fonction "destructeur" spéciale afin de libérer les données privées correctement.

Ou encore, si vous souhaitez que votre struct ne pas avoir de champs publics que ce soit, vous pouvez faire l'opaque struct toute , et juste avoir l'en-tête quelque chose comme

struct Foo; 

    extern struct Foo* Foo_Create(); /* "constructor" */ 

    extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */ 

Avec la définition réelle de struct Foo dans foo.c, et les fonctions getter et setter sont disponibles pour toutes les propriétés auxquelles vous souhaitez donner un accès direct.

+0

+1 Merci de m'avoir rappelé l'idiome de Pimple. Je n'ai jamais envisagé de l'appliquer au langage C. –

+2

Dans C99, vous pouvez envisager de remplacer le pointeur 'struct FooPrivate' par un membre de tableau flexible' struct FooPrivate p [] '. Cela empêchera l'utilisateur de l'interface de faire accidentellement une copie superficielle de 'struct Foo', car cela fera de' struct Foo' un type incomplet. – caf

+0

@caf Pouvez-vous développer ce qui change la réponse serait nécessaire pour utiliser des membres de tableau flexible à la place? – CMCDragonkai

0

Je suis désolé pour vous, parce que le mélange de différents concepts OO en C est souvent comme une cheville carrée dans un trou rond. Cela étant dit, GObject a le soutien des membres publics et privés, mais c'est l'une de mes architectures les moins préférées sur terre. Si vous n'êtes pas concerné par la baisse de performance mineure, vous pouvez faire une solution plus simple: avoir une structure secondaire remplie de membres privés et avoir un pointeur anonyme vers cette structure à partir de la structure principale (publique).

+1

Je voudrais rester à l'écart des paquets de bibliothèque car cela implique des problèmes de licence et le développement doit être terminé rapidement. –

+0

(a) GObject est une bibliothèque open-source, même si elle peut même présenter des problèmes de licence; (b) la structure privée secondaire ne nécessite pas de bibliothèque. – Reinderien

9

Le concept parfois utilisé dans C est

// lib.h 
typedef struct { 
    int publicInt; 
    //... 
    char * publicStr; 
} Public; 

Public * getPublic(); 
int function(Public * public); 

// lib.c 

typedef struct { 
    Public public; 
    int privateInt; 
    // ... 
    char * privateStr 
} Private; 

static Private * getPrivate(); 

Public * getPublic() { return (Public*) getPrivate(); } 
int function(Public * public) { 
    Private * private = (Private *) public; 
    // ... 
} 

Il utilise l'astuce standard qui peut être interchangés un pointeur vers une struct avec un pointeur vers le premier élément d'une struct.

Si vous voulez tous vos champs à caractère privé, il est encore plus facile:

// lib2.h 
typedef struct AllPrivate * Handle; 
Handle getHandle(); 
int function2(Handle handle); 

// lib2.c 
struct AllPrivate { /* ... */ } 

Les fichiers qui #include lib2.h ne se plaindra pas, puisque nous utilisons struct AllPrivate * et tous les pointeurs sont les mêmes taille, de sorte que le compilateur n'a pas besoin de connaître les entrailles de struct AllPrivate.

Pour faire une région protégée, vous auriez juste pour définir

// include/public.h 
struct Public { /* ... */ } 
struct Public * getPublic(); 
int somePublicFunction(struct Public *); 

// dev/include/protected.h 
struct Protected { struct Public public; /* ... */ } 
struct Protected * getProtected(); 
int someProtectedFunction(struct Protected *); 

// dev/src/private.c 
struct Private { struct Protected protected; /* ... * /} 
struct Public * getPublic() { return (struct Public *) getPrivate(); } 
struct Public * getProtected() { return (struct Protected *) getPrivate(); } 
int somePublicFunction(struct Public * public) { 
    struct Private private = (struct Private *) public; 
    // ... 
} 
int someProtectedFunction(struct Protected * protected) { 
    struct Private private = (struct Private *) protected; 
    // ... 
} 

Ensuite, il est juste une question de faire en sorte que dev/include n'est pas passé autour.

2

Pour les champs de données, ne les utilisez pas. Vous pouvez faire quelques trucs comme leur donner des noms fous pour décourager leur utilisation, mais cela n'arrêtera pas les gens. La seule façon de le faire est de créer une autre structure privée accessible par un pointeur vide par les fonctions de votre bibliothèque.

Pour les fonctions privées - utilisez les fonctions du fichier static. Mettez toutes les fonctions de votre bibliothèque dans un fichier C et déclarez celles que vous voulez privées comme static et ne les placez pas dans des fichiers d'en-tête.

1

Souvent, par convention, un membre privé a un caractère de soulignement supplémentaire dans son nom, ou quelque chose comme _pri ajouté. Ou peut-être un commentaire. Cette technique n'effectue pas de vérification imposée par le compilateur pour s'assurer que personne n'accède de manière inappropriée à ces champs, mais sert d'avertissement à quiconque lit une déclaration struct que les contenus sont des détails d'implémentation et qu'ils ne doivent pas y jeter un coup d'œil.

Une autre technique courante consiste à exposer votre structure comme un type incomplet. Par exemple, dans votre fichier d'en-tête, vous pourriez avoir:

struct my_struct; 

void some_function(struct my_struct *); 

Et dans la mise en œuvre, ou un en-tête interne non accessible aux consommateurs de la bibliothèque, vous avez:

struct my_struct 
{ 
    /* Members of that struct */ 
}; 

Vous pouvez Faites aussi des trucs similaires avec des pointeurs vides, qui seront lancés au bon endroit dans la partie "privée" du code. Cette approche perd de la souplesse (vous ne pouvez pas avoir une instance allouée par pile d'un type indéfini, par exemple), mais cela peut être acceptable. Si vous voulez avoir un mélange de membres privés et publics, vous pouvez faire la même chose que ci-dessus, mais stocker le pointeur structure struct en tant que membre du public, et le laisser incomplet dans les consommateurs publics de la bibliothèque . Cependant, cela introduit une certaine indirection, ce qui peut nuire aux performances. Il y a certains (généralement non-portable, mais travailleront sur des compilateurs raisonnables) astuces par type se calembour vous pouvez utiliser aussi:

struct public_struct 
{ 
    int public_member; 
    int public_member2; 
    /* etc.. */ 
}; 

struct private_struct 
{ 
    struct public_struct base_members; 

    int private_member1; 
    int private_member2; 
}; 

void some_function(struct public_struct *obj) 
{ 
    /* Hack alert! */ 
    struct private_struct *private = (struct private_struct*)obj; 
} 

Cela suppose aussi que vous ne pouvez pas stocker ces objets sur la pile ou dans le stockage statique , ou obtenez la taille au moment de la compilation.