Co to jest enkapsulacja czasu kompilacji w C?

9

Kiedy badałem zalety C w stosunku do C ++, natknąłem się na ten akapit:

Standardowym sposobem w C do enkapsulacji jest przekazanie do przodu deklaracji struktury i zezwalanie na dostęp do jej danych tylko poprzez funkcje. Ta metoda tworzy także enkapsulację czasu kompilacji. Hermetyzacja czasu kompilacji pozwala nam zmieniać elementy struktur danych bez ponownej kompilacji kodu klienta (inny kod korzystający z naszego interfejsu). Z drugiej strony standardowy sposób wykonywania enkapsulacji C ++ (przy użyciu klas) wymaga ponownej kompilacji kodu klienta podczas dodawania lub usuwania zmiennych prywatnych członków.

Rozumiem, w jaki sposób deklarowanie struktury i uzyskiwanie dostępu do jej członków za pomocą funkcji ukrywa szczegóły implementacji struktury. Nie rozumiem tylko tej linii:

Hermetyzacja czasu kompilacji pozwala nam zmieniać elementy struktur danych bez ponownej kompilacji kodu klienta (inny kod korzystający z naszego interfejsu).

W jakim scenariuszu ma to zastosowanie?

mbl
źródło
Zasadniczo structjest to czarna skrzynka z nieznanymi elementami wewnętrznymi. Jeśli klient nie zna elementów wewnętrznych, nigdy nie może uzyskać do nich bezpośredniego dostępu i można je dowolnie zmieniać. Jest to podobne do enkapsulacji w OOP. Elementy wewnętrzne są prywatne, a obiekt zmienia się tylko przy użyciu metod publicznych.
Sulthan
Nie zawsze jest to prawdą. Jeśli zdecydujesz się dodać / usunąć członków struktury, zmienisz jej rozmiar. Będzie to wymagało ponownej kompilacji kodu klienta.
DarkAtom,
2
@DarkAtom Nieprawda! Jeśli klient nie zna zawartości ( nieprzezroczysta struktura), to nie zna jego rozmiaru, więc zmiana rozmiaru nie stanowi problemu.
Adrian Mole
1
@DarkAtom: Zezwolenie na dostęp do struktury tylko za pomocą funkcji obejmuje przydział tylko za pomocą funkcji. Biblioteka zapewni funkcję do alokacji struktury, a klient nigdy nie pozna jej wielkości. Zmiana rozmiaru nie wymaga ponownej kompilacji klienta.
Eric Postpischil
3
Zauważ, że technicznie nie jest to „przewaga C nad C ++”, ponieważ możesz (i często robisz) wdrożyć ten sam pomysł w C ++. Wyszukaj idiom „pimpl” .
user4815162342,

Odpowiedzi:

4

Możliwym scenariuszem, w którym miałoby to miejsce, jest sytuacja, w której biblioteka bazy danych, napisana w czasach, gdy miejsce na dysku twardym było bardzo ograniczone, wykorzystywała jeden bajt do przechowywania pola „roku” daty (np. 11-NOV-1973 musiałby 73za rok). Ale kiedy nadszedł rok 2000, nie byłoby to już wystarczające, a rok musiał być zapisany jako krótka (16-bitowa) liczba całkowita. Odpowiednim (znacznie uproszczonym) nagłówkiem dla tej biblioteki może być:

// dbEntry.h
typedef struct _dbEntry dbEntry;

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

Program „kliencki” to:

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

„Oryginalna” implementacja:

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

Następnie, przy podejściu Y2K, ten plik implementacji zostałby zmieniony w następujący sposób (wszystko inne pozostało nietknięte):

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

Gdy klient musi zostać zaktualizowany w celu korzystania z nowej (bezpiecznej dla Y2K) wersji, zmiany kodu nie będą wymagane. W rzeczywistości, może nawet nie trzeba ponownie skompilować: po prostu re-łączenie do zaktualizowanej biblioteki obiektów (jeśli to, co to jest) może być wystarczająca.

Adrian Mole
źródło
2

Uwaga: Poniższa lista nie będzie wyczerpująca. Zmiany są mile widziane!

Obowiązujące scenariusze obejmują:

  • Aplikacje wielomodułowe, w których z jakiegoś powodu nie chcesz ponownej kompilacji.
  • Struktury używane w bibliotekach, w których nie chcesz zmuszać użytkowników biblioteki do ponownej kompilacji za każdym razem, gdy zmienisz (opublikowaną) strukturę.
  • Struktury zawierające różne elementy na różnych platformach, na których działa moduł.

Najbardziej znaną tego typu strukturą FILE. Wystarczy zadzwonić fopen()i uzyskać wskaźnik, jeśli się powiedzie. Ten wskaźnik jest następnie przekazywany do każdej innej funkcji, która działa na plikach. Ale nie znasz - i nie chcesz wiedzieć - szczegółów, takich jak zawarte elementy i rozmiar.

zajęty
źródło