Qu'est-ce que l'encapsulation au moment de la compilation en C?

9

Lorsque je recherchais les avantages du C par rapport à C ++, je suis tombé sur ce paragraphe:

La méthode standard en C pour effectuer l'encapsulation est de déclarer en avant une structure et de n'autoriser l'accès à ses données que par le biais de fonctions. Cette méthode crée également une encapsulation au moment de la compilation. L'encapsulation en temps de compilation nous permet de changer les structures de données des membres sans recompilation du code client (autre code utilisant notre interface). En revanche, la méthode standard d'encapsulation C ++ (à l'aide de classes) nécessite la recompilation du code client lors de l'ajout ou de la suppression de variables de membre privé.

Je comprends comment la déclaration d'une structure et l'accès à ses membres via des fonctions masquent les détails d'implémentation de la structure. Ce que je ne comprends pas, c'est cette ligne spécifiquement:

L'encapsulation en temps de compilation nous permet de changer les structures de données des membres sans recompilation du code client (autre code utilisant notre interface).

Dans quel scénario est-ce applicable?

mbl
la source
Fondamentalement, le structest une boîte noire avec des composants internes inconnus. Si le client ne connaît pas les internes, il ne pourra jamais y accéder directement et vous pourrez les changer à volonté. Ceci est similaire à l'encapsulation dans la POO. Les internes sont privés et vous ne modifiez l'objet qu'en utilisant des méthodes publiques.
Sulthan
Ce n'est pas toujours vrai. Si vous décidez d'ajouter / supprimer des membres d'une structure, vous changez sa taille. Cela nécessitera une recompilation du code client.
DarkAtom
2
@DarkAtom Pas vrai! Si le client ne connaît pas le contenu (une structure opaque ), il ne connaît pas sa taille, donc la modification de la taille n'est pas un problème.
Adrian Mole
1
@DarkAtom: Autoriser l'accès à une structure uniquement via des fonctions inclut l'allocation uniquement via des fonctions. La bibliothèque fournirait une fonction pour allouer une structure, et le client ne connaîtrait jamais sa taille. La modification de la taille ne nécessite pas de recompilation du client.
Eric Postpischil
3
Notez que ce n'est techniquement pas un "avantage du C par rapport à C ++" car vous pouvez (et le faites souvent) implémenter la même idée en C ++. Recherchez l' idiome "pimpl" .
user4815162342

Réponses:

4

Un scénario possible dans le monde réel où cela se produirait serait lorsqu'une bibliothèque de bases de données, écrite à l'époque où l'espace sur le disque dur était très limité, utilisait un seul octet pour stocker le champ 'année' d'une date (par exemple 11-NOV-1973 aurait 73pour l'année). Mais, au moment de l'an 2000, cela ne serait plus suffisant, et l'année devait alors être stockée sous la forme d'un entier court (16 bits). L'en-tête pertinent (très simplifié) de cette bibliothèque pourrait être le suivant:

// dbEntry.h
typedef struct _dbEntry dbEntry;

dbEntry* CreateDBE(int day, int month, int year, int otherData);
void DeleteDBE(dbEntry* entry);
int GetYear(dbEntry* entry);

Et un programme «client» serait:

#include <stdio.h>
#include "dbEntry.h"

int main()
{
    int dataBlob = 42;
    dbEntry* test = CreateDBE(17, 11, 2019, dataBlob);
    //...
    int year = GetYear(test);
    printf("Year = %d\n", year);
    //...
    DeleteDBE(test);
    return 0;
}

L'implémentation «originale»:

#include <stdlib.h>
#include "dbEntry.h"

struct _dbEntry {
    unsigned char d;
    unsigned char m;
    unsigned char y;    // Fails at Y2K!
    int dummyData;
};

dbEntry* CreateDBE(int day, int month, int year, int otherData)
{
    dbEntry* local = malloc(sizeof(dbEntry));
    local->d = (unsigned char)(day);
    local->m = (unsigned char)(month);
    local->y = (unsigned char)(year % 100);
    local->dummyData = otherData;
    return local;
}

void DeleteDBE(dbEntry* entry)
{
    free(entry);
}

int GetYear(dbEntry* entry)
{
    return (int)(entry->y);
}

Ensuite, à l'approche de Y2K, ce fichier d'implémentation serait modifié comme suit (tout le reste étant inchangé):

struct _dbEntry {
    unsigned char d;
    unsigned char m;
    unsigned short y;   // Can now differentiate 1969 from 2069
    int dummyData;
};

dbEntry* CreateDBE(int day, int month, int year, int otherData)
{
    dbEntry* local = malloc(sizeof(dbEntry));
    local->d = (unsigned char)(day);
    local->m = (unsigned char)(month);
    local->y = (unsigned short)(year);
    local->dummyData = otherData;
    return local;
}

Lorsque le client doit être mis à jour pour utiliser la nouvelle version (Y2K-safe), aucun changement de code n'est requis. En fait, vous n'aurez peut- être même pas à recompiler: une simple reconnexion à la bibliothèque d'objets mise à jour (si c'est ce que c'est) pourrait être suffisante.

Adrian Mole
la source
2

Remarque: La liste suivante ne sera pas exhaustive. Les modifications sont les bienvenues!

Les scénarios applicables incluent:

  • Applications multi-modules où vous ne voulez pas de recompilation pour une raison quelconque.
  • Structures utilisées dans les bibliothèques où vous ne souhaitez pas forcer les utilisateurs de la bibliothèque à recompiler chaque fois que vous modifiez une structure (publiée).
  • Structures qui contiennent différents éléments sur les différentes plates-formes sur lesquelles le module fonctionne.

La structure la plus connue de ce type est FILE. Vous venez d'appeler fopen()et d'obtenir un pointeur en cas de succès. Ce pointeur est ensuite remis à l'autre fonction qui fonctionne sur les fichiers. Mais vous ne connaissez pas - et vous ne voulez pas savoir - les détails, comme les éléments contenus et la taille.

l'occupé
la source