web-dev-qa-db-ja.com

C構造体のメンバーの非表示

私は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つの構造を他の構造にキャストできます。これは悪い習慣と見なされますか?それとも頻繁に行われますか?

59
Marlon

個人的に、私はこれがもっと好きです:

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

sizeof(SomeStruct) != sizeof(SomeStructSource)。これwill誰かがあなたを見つけて、いつかあなたを殺します。

47
hobbs

あなたはほとんどそれを持っていますが、十分に行っていません。

ヘッダー内:

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を割り当てたりすることもできません。もちろん、これも欠点と見なすことができます。

25
Logan Capaldo

パブリック構造体パターンの使用はお勧めしません。 OOPの正しい設計パターンは、すべてのデータにアクセスするための関数を提供し、データへのパブリックアクセスを許可しないことです。クラスデータは、プライベートであるために、ソースで宣言する必要があります、およびCreateDestroyがデータの割り当てと解放を行うような方法で参照されます。このような方法では、パブリック/プライベートのジレンマはもう存在しません。

/*********** 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 */
}
15
Felipe Lavratti

絶対にしないでください。 APIがパラメーターとしてSomeStructを使用するものをサポートしている場合(これは期待していることです)、スタックに割り当てて渡すことができます。クライアントクラスの割り当てにはスペースが含まれていません。

構造体のメンバーを非表示にする古典的な方法は、空にすることです*。基本的には、実装ファイルのみが知っているハンドル/ Cookieです。ほとんどすべてのCライブラリがプライベートデータに対してこれを行います。

9
NG.

あなたが提案した方法に似た何かが実際に時々使われます(例えば、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;
}
7
caf

隠し構造を作成し、パブリック構造のポインターを使用して参照します。たとえば、.hには次のものがあります。

typedef struct {
    int a, b;
    void *private;
} public_t;

そして、あなたの.c:

typedef struct {
    int c, d;
} private_t;

それは明らかにポインタ演算を保護せず、割り当て/割り当て解除のために少しオーバーヘッドを追加しますが、私はそれが問題の範囲を超えていると思います。

4
jweyrich

次の回避策を使用します。

#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
3

これを行うには、void *パブリック構造体のプライベート構造体へのポインター。あなたがそれをしている方法は、コンパイラをだましています。

2
user181548

このアプローチは、有効で便利な標準Cです。

BSD Unixで定義されたソケットAPIで使用されるわずかに異なるアプローチは、struct sockaddr

1
Heath Hunnicutt

呼び出し元のコードが(SomeStructSource *)。また、別のパブリックメンバーを追加する場合はどうなりますか?バイナリ互換性を破る必要があります。

編集:それが.cファイルにあったことを逃しましたが、クライアントがそれをコピーするのを止めるものは本当にありません。おそらく#includeing .cファイルを直接。

0

関連しているが、正確に隠れているわけではない。

条件付きでメンバーを非推奨にすることです。

これは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;
}
0
ideasman42

私の解決策は、内部構造体のプロトタイプのみを提供し、.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
};

注:その場合、コンパイラーは内部構造体のサイズを知ることができないため、変数はポインターでなければなりません。

0
adc