Что такое инкапсуляция во время компиляции в C?

9

Когда я исследовал преимущества C над C ++, я наткнулся на этот параграф:

Стандартным способом инкапсуляции в C является предварительное объявление структуры и доступ к ее данным только через функции. Этот метод также создает инкапсуляцию времени компиляции. Инкапсуляция времени компиляции позволяет нам изменять элементы структур данных без перекомпиляции клиентского кода (другой код использует наш интерфейс). С другой стороны, стандартный способ инкапсуляции C ++ (с использованием классов) требует перекомпиляции клиентского кода при добавлении или удалении закрытых переменных-членов.

Я понимаю, как упреждающее объявление структуры и доступ к ее членам через функции скрывает детали реализации структуры. Что я не понимаю, так это в частности:

Инкапсуляция времени компиляции позволяет нам изменять элементы структур данных без перекомпиляции клиентского кода (другой код использует наш интерфейс).

В каком сценарии это применимо?

MBL
источник
По сути, structэто черный ящик с неизвестными внутренностями. Если клиент не знает внутренности, он никогда не сможет получить к ним доступ напрямую, и вы можете изменить их по своему желанию. Это похоже на инкапсуляцию в ООП. Внутренние компоненты являются частными, и вы можете изменить объект только с помощью открытых методов.
Sulthan
Это не всегда верно. Если вы решите добавить / удалить элементы структуры, вы измените ее размер. Это потребует перекомпиляции клиентского кода.
DarkAtom
2
@DarkAtom Не правда! Если клиент не знает содержимого ( непрозрачная структура), то он не знает его размера, поэтому изменение размера не является проблемой.
Адриан Моул
1
@DarkAtom: разрешение доступа к структуре только через функции включает в себя распределение только через функции. Библиотека предоставит функцию для выделения структуры, и клиент никогда не узнает ее размера. Изменение размера не требует перекомпиляции клиента.
Эрик Постпищил
3
Обратите внимание, что это технически не является «преимуществом C над C ++», поскольку вы можете (и часто делаете) реализовать ту же идею в C ++. Посмотрите на идиому "прыщ" .
user4815162342

Ответы:

4

Возможный сценарий реальной ситуации, когда это происходит, когда библиотека базы данных, написанная в дни, когда пространство на жестком диске было очень ограничено, использовала один байт для хранения поля даты в году (например, 11-NOV-1973 бы 73на год). Но когда наступил 2000 год, этого уже было бы недостаточно, и тогда год должен был быть сохранен как короткое (16-разрядное) целое число. Соответствующий (очень упрощенный) заголовок для этой библиотеки может быть таким:

// dbEntry.h
typedef struct _dbEntry dbEntry;

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

И «клиентская» программа будет:

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

«Оригинальная» реализация:

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

Затем, при приближении 2000 года, этот файл реализации будет изменен следующим образом (все остальное остается без изменений):

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

Когда клиент должен быть обновлен для использования новой (безопасной для Y2K) версии, никаких изменений кода не потребуется. На самом деле, вам даже может не потребоваться повторная компиляция: может быть достаточно простой повторной ссылки на обновленную библиотеку объектов (если это так) .

Адриан Моул
источник
2

Примечание. Следующий список не является исчерпывающим. Изменения приветствуются!

К применимым сценариям относятся:

  • Мультимодульные приложения, где вы не хотите перекомпиляции по какой-то причине.
  • Структуры, используемые в библиотеках, когда вы не хотите заставлять пользователей библиотеки перекомпилировать каждый раз, когда вы изменяете (опубликованную) структуру.
  • Структуры, которые содержат разные элементы на разных платформах, над которыми работает модуль.

Наиболее известной структурой такого рода является FILE. Вы просто позвоните fopen()и получите указатель в случае успеха. Этот указатель затем передается каждой другой функции, которая работает с файлами. Но вы не знаете - и не хотите знать - детали, такие как содержащиеся элементы и размер.

занятая пчела
источник