O que é o encapsulamento em tempo de compilação em C?

9

Quando eu estava pesquisando as vantagens de C sobre C ++, deparei-me com este parágrafo:

A maneira padrão em C de fazer o encapsulamento é encaminhar declarar uma estrutura e permitir apenas o acesso aos seus dados através de funções. Este método também cria encapsulamento em tempo de compilação. O encapsulamento em tempo de compilação nos permite alterar os membros das estruturas de dados sem recompilar o código do cliente (outro código usando nossa interface). A maneira padrão de encapsular o C ++, por outro lado (usando classes) requer a recompilação do código do cliente ao adicionar ou remover variáveis ​​de membros particulares.

Eu entendo como a declaração de uma estrutura e o acesso a seus membros por meio de funções ocultam os detalhes de implementação da estrutura. O que eu não entendo é essa linha especificamente:

O encapsulamento em tempo de compilação nos permite alterar os membros das estruturas de dados sem recompilar o código do cliente (outro código usando nossa interface).

Em que cenário isso é aplicável?

mbl
fonte
Basicamente, structé uma caixa preta com componentes internos desconhecidos. Se o cliente não souber os internos, ele nunca poderá acessá-los diretamente e você poderá alterá-los à vontade. Isso é semelhante ao encapsulamento no OOP. Os internos são privados e você só altera o objeto usando métodos públicos.
Sulthan
Isto não é sempre verdade. Se você decidir adicionar / remover membros de uma estrutura, altere seu tamanho. Isso exigirá a recompilação do código do cliente.
DarkAtom 17/11/19
2
@DarkAtom Não é verdade! Se o cliente não conhece o conteúdo (uma estrutura opaca ), não conhece seu tamanho, portanto, alterar o tamanho não é um problema.
Adrian Mole
11
@DarkAtom: permitir acesso a uma estrutura somente através de funções inclui alocação apenas através de funções. A biblioteca forneceria uma função para alocar uma estrutura, e o cliente nunca saberia seu tamanho. Alterar o tamanho não requer recompilar o cliente.
22819 Eric Postpischil
3
Observe que tecnicamente não é uma "vantagem do C sobre o C ++", pois você pode (e costuma fazer) implementar a mesma idéia no C ++. Procure o idioma "pimpl" .
user4815162342

Respostas:

4

Um possível cenário do mundo real onde isso ocorreria é quando uma biblioteca de banco de dados, escrita nos dias em que o espaço no disco rígido era muito limitado, usava um único byte para armazenar o campo 'ano' de uma data (por exemplo, 11 de novembro de 1973 teria 73para o ano). Mas, quando o ano 2000 chegou, isso não seria mais suficiente e o ano teve que ser armazenado como um número inteiro curto (16 bits). O cabeçalho relevante (muito simplificado) para esta biblioteca pode ser o seguinte:

// dbEntry.h
typedef struct _dbEntry dbEntry;

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

E um programa 'cliente' seria:

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

A implementação 'original':

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

Em seguida, na abordagem do Y2K, esse arquivo de implementação seria alterado da seguinte maneira (tudo o mais permanece intocado):

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

Quando o cliente precisar ser atualizado para usar a nova versão (segura para o Y2K), nenhuma alteração de código será necessária. De fato, talvez você nem precise recompilar: basta vincular novamente a biblioteca de objetos atualizada (se é isso que é) a ser suficiente.

Adrian Mole
fonte
2

Nota: A lista a seguir não será exaustiva. As edições são bem-vindas!

Os cenários aplicáveis ​​incluem:

  • Aplicativos com vários módulos nos quais você não deseja recompilação por algum motivo.
  • Estruturas usadas em bibliotecas nas quais você não deseja forçar os usuários da biblioteca a recompilar cada vez que você altera uma estrutura (publicada).
  • Estruturas que contêm elementos diferentes nas diferentes plataformas nas quais o módulo trabalha.

A estrutura mais conhecida desse tipo é FILE. Você acabou de ligar fopen()e obter um ponteiro, se for bem-sucedido. Esse ponteiro é então entregue a outra função que funciona em arquivos. Mas você não sabe - e não quer saber - os detalhes, como elementos contidos e tamanho.

the busybee
fonte