Cでのコンパイル時のカプセル化とは何ですか?


9

C ++に対するCの利点を調査していたとき、この段落に出くわしました。

Cでカプセル化を行う標準的な方法は、構造体を転送宣言し、関数を介してそのデータへのアクセスのみを許可することです。このメソッドは、コンパイル時のカプセル化も作成します。コンパイル時のカプセル化により、クライアントコード(インターフェイスを使用する他のコード)を再コンパイルすることなく、データ構造のメンバーを変更できます。一方、(クラスを使用して)カプセル化C ++を行う標準的な方法では、プライベートメンバー変数を追加または削除するときにクライアントコードを再コンパイルする必要があります。

構造体を宣言し、関数を介してそのメンバーにアクセスすることで、構造体の実装の詳細がどのように隠されるかを理解しています。私が理解していないのは、特にこの行です:

コンパイル時のカプセル化により、クライアントコード(インターフェイスを使用する他のコード)を再コンパイルすることなく、データ構造のメンバーを変更できます。

これはどのシナリオに当てはまりますか?


基本的にstructは、内部が不明なブラックボックスです。クライアントが内部を知らない場合、クライアントは直接それらにアクセスできず、自由に変更できます。これは、OOPのカプセル化に似ています。内部はプライベートであり、パブリックメソッドを使用してのみオブジェクトを変更します。
スルタン

これは常に正しいとは限りません。構造体のメンバーを追加/削除する場合は、そのサイズを変更します。これには、クライアントコードの再コンパイルが必要です。
DarkAtom

2
@DarkAtom真実ではない!クライアントがコンテンツ(不透明な構造)を知らない場合、そのサイズを知らないので、サイズを変更しても問題ありません。
Adrian Mole

1
@DarkAtom:関数を通じてのみ構造へのアクセスを許可すると、関数を通じてのみ割り当てが行われます。ライブラリは構造を割り当てる関数を提供し、クライアントはそのサイズを知ることはありません。サイズを変更しても、クライアントを再コンパイルする必要はありません。
Eric Postpischil

3
これは技術的にはC ++でのCの利点ではないことに注意してください。これは、C ++で同じアイデアを実装できる(そして、しばしばそうする)ためです。見上げて「PIMPL」イディオムを
user4815162342

回答:


4

これが発生する可能性のある現実的なシナリオは、ハードディスク領域が非常に制限されていた日に書かれたデータベースライブラリが1バイトを使用して日付の「年」フィールドを格納した場合です(例: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);
}

次に、Y2Kのアプローチで、この実装ファイルは次のように変更されます(その他はすべて変更されません)。

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()成功した場合は、呼び出してポインタを取得するだけです。このポインタは、ファイルで機能する他の関数に渡されます。しかし、あなたは知りません-知りたくありません-含まれている要素やサイズなどの詳細。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.