17

C++ および Java では、データ構造にprivatepublicおよびprotected領域を含めることができます。この概念を、私が書いている C 言語プログラムに移植したいと思います。

C でプライベートまたは保護された関数ポインターとデータ フィールドを実装するためのイディオムはありますstructか? 私は Cstructが公開されていることを知っています。実装の詳細を隠し、ユーザーに公開インターフェースを使用させるのに役立つイディオムを探しています。

注: 言語はショップによって選択されているため、オブジェクト指向の概念を C に実装するのに行き詰まっています。

ありがとう。

4

5 に答える 5

30

ご存知のように、これはできません。ただし、同様の効果を可能にするイディオムがあります。

C では、オブジェクト指向設計で "pimpl" イディオムとして知られているものと同様のことを行うことができます。構造体は、構造体のプライベート データとして機能する別の前方宣言された構造体への不透明なポインターを持つことができます。メンバー関数の代わりに構造体を操作する関数は、プライベート メンバーの完全な定義を持つことができ、それを利用できますが、コードの他の部分はできません。例えば:

ヘッダーの foo.h:

  struct FooPrivate;

  struct Foo {
     /* public: */
       int x; 
       double y;
     /* private: */
       struct FooPrivate* p;
  };

  extern struct Foo* Foo_Create(); /* "constructor" */

  extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */

実装では、foo.c:

  struct FooPrivate {
     int z;
  };

  struct Foo* Foo_Create()
  {
     struct Foo* foo = malloc(sizeof(Foo));

     foo->p = malloc(sizeof(FooPrivate));

     foo->x = 0;
     foo->y = 0;
     foo->p->z = 0;

     return foo;
  }

  void Foo_DoWhatever(struct Foo* foo) 
  {
      foo->p->z = 4; /* Can access "private" parts of foo */
  }

プログラムで:

  #include "foo.h"

  int main()
  {
      struct Foo* foo = Foo_Create();

      foo->x = 100; /* Can access "public" parts of foo */
      foo->p->z = 20; /* Error! FooPrivate is not fully declared here! */

      Foo_DoWhatever(foo); /* Can call "member" function */

      return 0;
  }

プライベート データにメモリを割り当てるには、「コンストラクタ」関数を使用する必要があることに注意してください。明らかに、プライベート データの割り当てを適切に解除するには、これを特別な「デストラクタ」関数と組み合わせる必要があります。

または、代わりに、構造体にパブリックフィールドをまったく持たないようにしたい場合は、構造体全体を不透明にして、ヘッダーを次のようにすることができます

  struct Foo;

  extern struct Foo* Foo_Create(); /* "constructor" */

  extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */

foo.c での実際の定義struct Foo、および直接アクセスを提供したい任意のプロパティで使用可能な getter および setter 関数を使用します。

于 2010-09-29T17:50:13.630 に答える
10

Cで時々使用される概念は

// lib.h
typedef struct {
  int publicInt;
  //...
  char * publicStr;
} Public;

Public * getPublic();
int function(Public * public);

// lib.c

typedef struct {
  Public public;
  int privateInt;
  // ...
  char * privateStr
} Private;

static Private * getPrivate();

Public * getPublic() { return (Public*) getPrivate(); }
int function(Public * public) {
  Private * private = (Private *) public;
  // ...
}

これは、構造体へのポインターを構造体の最初の要素へのポインターと交換できるという標準的なトリックを使用します。

すべてのフィールドを非公開にしたい場合は、さらに簡単です。

// lib2.h
typedef struct AllPrivate * Handle;
Handle getHandle();
int function2(Handle handle);

// lib2.c
struct AllPrivate { /* ... */ }

のみを使用し、すべてのポインターが同じサイズであるため、#includelib2.h が文句を言わないファイル。struct AllPrivate *struct AllPrivate

保護された領域を実行するには、定義するだけです

// include/public.h
struct Public { /* ... */ }
struct Public * getPublic();
int somePublicFunction(struct Public *);

// dev/include/protected.h
struct Protected { struct Public public; /* ... */ }
struct Protected * getProtected();
int someProtectedFunction(struct Protected *);

// dev/src/private.c
struct Private { struct Protected protected; /* ... * /}
struct Public * getPublic() { return (struct Public *) getPrivate(); }
struct Public * getProtected() { return (struct Protected *) getPrivate(); }
int somePublicFunction(struct Public * public) { 
  struct Private private = (struct Private *) public;
  // ...
}
int someProtectedFunction(struct Protected * protected) { 
  struct Private private = (struct Private *) protected;
  // ...
}

次に、それdev/includeが渡されないようにするだけです。

于 2010-09-29T17:46:46.277 に答える
2

データ フィールドの場合 -- 使用しないでください。クレイジーな名前を付けて使用を思いとどまらせるなど、いくつかのトリックを実行できますが、それで人々を止めることはできません. これを行う唯一の実際の方法は、ライブラリ関数によって void ポインターによってアクセスされる別のプライベート構造体を作成することです。

プライベート関数の場合 -- ファイルstatic関数を使用します。すべてのライブラリ関数を 1 つの C ファイルに入れ、プライベートにしたいものを宣言し、staticヘッダー ファイルには入れないでください。

于 2010-09-29T17:46:46.913 に答える
1

多くの場合、慣例により、プライベート メンバーの名前にはアンダースコアが追加されるか、そのようなものが_pri追加されます。または、おそらくコメント。この手法は、誰もそれらのフィールドに不適切にアクセスしないようにするためのコンパイラ強制チェックを行いませんが、struct宣言を読んでいる人に対して、内容は実装の詳細であり、それらをのぞき見したり突き刺したりしてはならないという警告として機能します。

もう 1 つの一般的な手法は、構造体を不完全な型として公開することです。たとえば、ヘッダー ファイルには次のようなものがあります。

struct my_struct;

void some_function(struct my_struct *);

そして、実装、またはライブラリの消費者がアクセスできない内部ヘッダーには、次のものがあります。

struct my_struct
{
    /* Members of that struct */
};

コードの「プライベート」部分の適切な場所にキャストされる void ポインターを使用して、同様のトリックを行うこともできます。このアプローチはある程度の柔軟性を失います (たとえば、未定義の型のスタック割り当てインスタンスを持つことはできません) が、それは許容できる場合があります。

プライベート メンバーとパブリック メンバーを混在させたい場合は、上記と同じことを行うことができますが、プライベート struct ポインターをパブリック メンバーのメンバーとして格納し、ライブラリのパブリック コンシューマーでは不完全なままにします。

ただし、これにより間接化が発生し、パフォーマンスが低下する可能性があります。いくつかの (通常は移植性がありませんが、適切なコンパイラで動作します) 型パニング トリックも使用できます。

struct public_struct
{
   int public_member;
   int public_member2;
   /* etc.. */
};

struct private_struct
{
   struct public_struct base_members;

   int private_member1;
   int private_member2;
};

void some_function(struct public_struct *obj)
{
   /* Hack alert! */
   struct private_struct *private = (struct private_struct*)obj;
}

これは、これらのオブジェクトをスタックまたは静的ストレージに格納できないこと、またはコンパイル時にサイズを取得できないことも前提としています。

于 2010-09-29T18:03:21.463 に答える
0

残念ながら、さまざまな OO の概念を C にマッシュアップすることは、丸い穴に四角い釘を刺すようなものです。そうは言っGObjectても、パブリック メンバーとプライベート メンバーをサポートしていますが、地球上で最も好きでないアーキテクチャの 1 つです。マイナーなパフォーマンス ヒットを気にしない場合は、より簡単な解決策を実行できる可能性があります。プライベート メンバーで満たされたセカンダリ構造体を持ち、プライマリ (パブリック) 構造体からその構造体への匿名ポインターを持ちます。

于 2010-09-29T17:43:35.953 に答える