私はCでOOPについて読んでいますが、C++のようにプライベートデータメンバーを持たないことが好きではありませんでした。しかし、2つの構造を作成できることに気付きました。 。1つはヘッダーファイルで定義され、もう1つはソースファイルで定義されます。
// =========================================
// in somestruct.h
typedef struct {
int _public_member;
} SomeStruct;
// =========================================
// in somestruct.c
#include "somestruct.h"
typedef struct {
int _public_member;
int _private_member;
} SomeStructSource;
SomeStruct *SomeStruct_Create()
{
SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource));
p->_private_member = 42;
return (SomeStruct *)p;
}
ここから、1つの構造を他の構造にキャストできます。これは悪い習慣と見なされますか?それとも頻繁に行われますか?
個人的に、私はこれがもっと好きです:
typedef struct {
int _public_member;
/*I know you wont listen, but don't ever touch this member.*/
int _private_member;
} SomeStructSource;
結局、Cです。もし人々が台無しにしたいなら、彼らは許されるべきです-ものを隠す必要はありません:
必要なのがABI/API互換性を維持することである場合、私が見たものからより一般的な2つのアプローチがあります。
クライアントに構造体へのアクセスを許可せず、不透明なハンドル(きれいな名前のvoid *)を与え、すべてにinit/destroyおよびaccessor関数を提供します。これにより、ライブラリを作成している場合、クライアントを再コンパイルしなくても構造を変更できます。
構造体の一部として不透明なハンドルを提供します。これは好きなように割り当てることができます。このアプローチは、ABI互換性を提供するためにC++でも使用されます。
例えば
struct SomeStruct {
int member;
void* internals; //allocate this to your private struct
};
sizeof(SomeStruct) != sizeof(SomeStructSource)
。これwill誰かがあなたを見つけて、いつかあなたを殺します。
あなたはほとんどそれを持っていますが、十分に行っていません。
ヘッダー内:
struct SomeStruct;
typedef struct SomeStruct *SomeThing;
SomeThing create_some_thing();
destroy_some_thing(SomeThing thing);
int get_public_member_some_thing(SomeThing thing);
void set_public_member_some_thing(SomeThing thing, int value);
.c内:
struct SomeStruct {
int public_member;
int private_member;
};
SomeThing create_some_thing()
{
SomeThing thing = malloc(sizeof(*thing));
thing->public_member = 0;
thing->private_member = 0;
return thing;
}
... etc ...
ポイントは、ここで消費者はno SomeStructの内部の知識を持っていることであり、消費者が再コンパイルする必要なく、自由にメンバーを追加および削除できます。また、メンバーを直接「誤って」変更したり、スタックにSomeStructを割り当てたりすることもできません。もちろん、これも欠点と見なすことができます。
パブリック構造体パターンの使用はお勧めしません。 OOPの正しい設計パターンは、すべてのデータにアクセスするための関数を提供し、データへのパブリックアクセスを許可しないことです。クラスデータは、プライベートであるために、ソースで宣言する必要があります、およびCreate
とDestroy
がデータの割り当てと解放を行うような方法で参照されます。このような方法では、パブリック/プライベートのジレンマはもう存在しません。
/*********** header.h ***********/
typedef struct sModuleData module_t'
module_t *Module_Create();
void Module_Destroy(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);
/*********** source.c ***********/
struct sModuleData {
/* private data */
};
module_t *Module_Create()
{
module_t *inst = (module_t *)malloc(sizeof(struct sModuleData));
/* ... */
return inst;
}
void Module_Destroy(module_t *inst)
{
/* ... */
free(inst);
}
/* Other functions implementation */
一方、Malloc/Free(状況によっては不必要なオーバーヘッドになる可能性があります)を使用したくない場合は、プライベートファイルで構造体を非表示にすることをお勧めします。プライベートメンバーはアクセス可能ですが、それはユーザーの責任です。
/*********** privateTypes.h ***********/
/* All private, non forward, datatypes goes here */
struct sModuleData {
/* private data */
};
/*********** header.h ***********/
#include "privateTypes.h"
typedef struct sModuleData module_t;
void Module_Init(module_t *);
void Module_Deinit(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);
/*********** source.c ***********/
void Module_Init(module_t *inst)
{
/* perform initialization on the instance */
}
void Module_Deinit(module_t *inst)
{
/* perform deinitialization on the instance */
}
/*********** main.c ***********/
int main()
{
module_t mod_instance;
module_Init(&mod_instance);
/* and so on */
}
絶対にしないでください。 APIがパラメーターとしてSomeStructを使用するものをサポートしている場合(これは期待していることです)、スタックに割り当てて渡すことができます。クライアントクラスの割り当てにはスペースが含まれていません。
構造体のメンバーを非表示にする古典的な方法は、空にすることです*。基本的には、実装ファイルのみが知っているハンドル/ Cookieです。ほとんどすべてのCライブラリがプライベートデータに対してこれを行います。
あなたが提案した方法に似た何かが実際に時々使われます(例えば、struct sockaddr*
はBSDソケットAPIで)、しかしC99の厳格なエイリアスルールに違反せずに使用することはほとんど不可能です。
ただし、安全に行うことができます:
somestruct.h
:
struct SomeStructPrivate; /* Opaque type */
typedef struct {
int _public_member;
struct SomeStructPrivate *private;
} SomeStruct;
somestruct.c
:
#include "somestruct.h"
struct SomeStructPrivate {
int _member;
};
SomeStruct *SomeStruct_Create()
{
SomeStruct *p = malloc(sizeof *p);
p->private = malloc(sizeof *p->private);
p->private->_member = 0xWHATEVER;
return p;
}
隠し構造を作成し、パブリック構造のポインターを使用して参照します。たとえば、.hには次のものがあります。
typedef struct {
int a, b;
void *private;
} public_t;
そして、あなたの.c:
typedef struct {
int c, d;
} private_t;
それは明らかにポインタ演算を保護せず、割り当て/割り当て解除のために少しオーバーヘッドを追加しますが、私はそれが問題の範囲を超えていると思います。
次の回避策を使用します。
#include <stdio.h>
#define C_PRIVATE(T) struct T##private {
#define C_PRIVATE_END } private;
#define C_PRIV(x) ((x).private)
#define C_PRIV_REF(x) (&(x)->private)
struct T {
int a;
C_PRIVATE(T)
int x;
C_PRIVATE_END
};
int main()
{
struct T t;
struct T *tref = &t;
t.a = 1;
C_PRIV(t).x = 2;
printf("t.a = %d\nt.x = %d\n", t.a, C_PRIV(t).x);
tref->a = 3;
C_PRIV_REF(tref)->x = 4;
printf("tref->a = %d\ntref->x = %d\n", tref->a, C_PRIV_REF(tref)->x);
return 0;
}
結果は次のとおりです。
t.a = 1
t.x = 2
tref->a = 3
tref->x = 4
これを行うには、void *
パブリック構造体のプライベート構造体へのポインター。あなたがそれをしている方法は、コンパイラをだましています。
このアプローチは、有効で便利な標準Cです。
BSD Unixで定義されたソケットAPIで使用されるわずかに異なるアプローチは、struct sockaddr
。
呼び出し元のコードが(SomeStructSource *)
。また、別のパブリックメンバーを追加する場合はどうなりますか?バイナリ互換性を破る必要があります。
編集:それが.cファイルにあったことを逃しましたが、クライアントがそれをコピーするのを止めるものは本当にありません。おそらく#include
ing .cファイルを直接。
関連しているが、正確に隠れているわけではない。
条件付きでメンバーを非推奨にすることです。
これはGCC/Clangで機能しますが、MSVCや他のコンパイラも廃止される可能性があるため、より移植性の高いバージョンを考え出すことができます。
かなり厳密な警告、またはエラーとしての警告を使用してビルドする場合、これにより少なくとも偶発的な使用が回避されます。
// =========================================
// in somestruct.h
#ifdef _IS_SOMESTRUCT_C
# if defined(__GNUC__)
# define HIDE_MEMBER __attribute__((deprecated))
# else
# define HIDE_MEMBER /* no hiding! */
# endif
#else
# define HIDE_MEMBER
#endif
typedef struct {
int _public_member;
int _private_member HIDE_MEMBER;
} SomeStruct;
#undef HIDE_MEMBER
// =========================================
// in somestruct.c
#define _IS_SOMESTRUCT_C
#include "somestruct.h"
SomeStruct *SomeStruct_Create()
{
SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource));
p->_private_member = 42;
return (SomeStruct *)p;
}
私の解決策は、内部構造体のプロトタイプのみを提供し、.cファイルで定義を宣言することです。 Cインターフェイスを表示し、C++を背後で使用するのに非常に便利です。
.h:
struct internal;
struct foo {
int public_field;
struct internal *_internal;
};
.c:
struct internal {
int private_field; // could be a C++ class
};
注:その場合、コンパイラーは内部構造体のサイズを知ることができないため、変数はポインターでなければなりません。