Was ist die Kapselung zur Kompilierungszeit in C?

9

Als ich die Vorteile von C gegenüber C ++ untersuchte, stieß ich auf diesen Absatz:

Die Standardmethode in C zur Kapselung besteht darin, eine Struktur weiterzuleiten und den Zugriff auf ihre Daten nur über Funktionen zuzulassen. Diese Methode erstellt auch eine Kapselung zur Kompilierungszeit. Die Kapselung der Kompilierungszeit ermöglicht es uns, die Mitglieder der Datenstrukturen zu ändern, ohne den Clientcode neu zu kompilieren (anderer Code über unsere Schnittstelle). Die Standardmethode für die Kapselung von C ++ (unter Verwendung von Klassen) erfordert andererseits die Neukompilierung des Clientcodes beim Hinzufügen oder Entfernen von privaten Mitgliedsvariablen.

Ich verstehe, wie das Vorwärtsdeklarieren einer Struktur und der Zugriff auf ihre Mitglieder über Funktionen die Implementierungsdetails der Struktur verbirgt. Was ich nicht verstehe, ist diese Zeile speziell:

Die Kapselung der Kompilierungszeit ermöglicht es uns, die Mitglieder der Datenstrukturen zu ändern, ohne den Clientcode neu zu kompilieren (anderer Code über unsere Schnittstelle).

In welchem ​​Szenario gilt dies?

mbl
quelle
Grundsätzlich structhandelt es sich um eine Black Box mit unbekannten Interna. Wenn der Client die Interna nicht kennt, kann er niemals direkt darauf zugreifen und Sie können sie nach Belieben ändern. Dies ähnelt der Einkapselung in OOP. Die Interna sind privat und Sie ändern das Objekt nur mit öffentlichen Methoden.
Sulthan
Das ist nicht immer wahr. Wenn Sie Elemente einer Struktur hinzufügen / entfernen möchten, ändern Sie deren Größe. Dies erfordert eine Neukompilierung des Client-Codes.
DarkAtom
2
@ DarkAtom Nicht wahr! Wenn der Client den Inhalt nicht kennt (eine undurchsichtige Struktur), kennt er seine Größe nicht, sodass das Ändern der Größe kein Problem darstellt.
Adrian Mole
1
@DarkAtom: Das Zulassen des Zugriffs auf eine Struktur nur über Funktionen umfasst die Zuweisung nur über Funktionen. Die Bibliothek würde eine Funktion zum Zuweisen einer Struktur bereitstellen, und der Client würde niemals ihre Größe kennen. Das Ändern der Größe erfordert kein erneutes Kompilieren des Clients.
Eric Postpischil
3
Beachten Sie, dass dies technisch gesehen kein "Vorteil von C gegenüber C ++" ist, da Sie dieselbe Idee in C ++ implementieren können (und dies häufig tun). Schauen Sie die up "Pimpl" Idiom .
user4815162342

Antworten:

4

Ein mögliches reales Szenario, in dem dies auftreten würde, besteht darin, dass eine Datenbankbibliothek, die in den Tagen geschrieben wurde, als der Festplattenspeicher sehr begrenzt war, ein einzelnes Byte zum Speichern des Felds "Jahr" eines Datums verwendete (z. B. 11. November 1973) hätte 73für das Jahr). Als das Jahr 2000 kam, würde dies jedoch nicht mehr ausreichen, und das Jahr musste dann als kurze (16-Bit) Ganzzahl gespeichert werden. Der relevante (stark vereinfachte) Header für diese Bibliothek könnte folgender sein:

// dbEntry.h
typedef struct _dbEntry dbEntry;

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

Und ein "Client" -Programm wäre:

#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;
}

Die "ursprüngliche" Implementierung:

#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);
}

Beim Ansatz von Y2K würde diese Implementierungsdatei dann wie folgt geändert (alles andere bleibt unberührt):

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;
}

Wenn der Client aktualisiert werden muss, um die neue (Y2K-sichere) Version zu verwenden, sind keine Codeänderungen erforderlich. Möglicherweise müssen Sie nicht einmal neu kompilieren: Eine einfache erneute Verknüpfung mit der aktualisierten Objektbibliothek (sofern dies der Fall ist) kann ausreichen.

Adrian Mole
quelle
2

Hinweis: Die folgende Liste erhebt keinen Anspruch auf Vollständigkeit. Änderungen sind willkommen!

Die anwendbaren Szenarien umfassen:

  • Anwendungen mit mehreren Modulen, bei denen Sie aus irgendeinem Grund keine Neukompilierung wünschen.
  • Strukturen, die in Bibliotheken verwendet werden, in denen Sie die Benutzer der Bibliothek nicht zwingen möchten, jedes Mal neu zu kompilieren, wenn Sie eine (veröffentlichte) Struktur ändern.
  • Strukturen, die unterschiedliche Elemente auf den verschiedenen Plattformen enthalten, auf denen das Modul arbeitet.

Die bekannteste Struktur dieser Art ist FILE. Sie rufen einfach an fopen()und erhalten einen Zeiger, wenn Sie erfolgreich sind. Dieser Zeiger wird dann an die andere Funktion übergeben, die an Dateien arbeitet. Aber Sie kennen die Details wie die enthaltenen Elemente und die Größe nicht - und Sie möchten es auch nicht wissen.

der Busybee
quelle