72

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

4

15 に答える 15

54

sizeof(SomeStruct) != sizeof(SomeStructSource)。これにより、誰かがあなたを見つけて、いつかあなたを殺害するでしょう。

于 2010-04-20T01:47:23.477 に答える
44

個人的に、私はこれがもっと欲しいです:

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
 };
于 2010-04-20T01:57:35.777 に答える
27

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

ヘッダー内:

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 ...

重要なのは、ここで消費者はSomeStructの内部を知らないので、消費者が再コンパイルする必要がなくても、メンバーを自由に追加および削除して、免責で変更できます。また、メンバーを「誤って」直接変更したり、SomeStructをスタックに割り当てたりすることもできません。もちろん、これは不利な点と見なすこともできます。

于 2010-04-20T01:58:06.867 に答える
20

publicstructパターンの使用はお勧めしません。Cの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 */
}
于 2013-03-11T14:17:34.927 に答える
9

絶対にしないでください。APIがSomeStructをパラメーターとして受け取るものをサポートしている場合(私はそれを期待しています)、それをスタックに割り当てて渡すことができます。コンパイラーの1つ以降、プライベートメンバーにアクセスしようとすると大きなエラーが発生します。クライアントクラスの割り当てには、そのためのスペースが含まれていません。

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

于 2010-04-20T01:50:36.670 に答える
7

提案した方法に似たものが実際に使用されることもありますが(たとえば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;
}
于 2010-04-20T02:11:03.957 に答える
4

非表示の構造を記述し、パブリック構造のポインターを使用してそれを参照します。たとえば、.hには次のものがあります。

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

そしてあなたの.c:

typedef struct {
    int c, d;
} private_t;

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

于 2010-04-20T02:04:23.060 に答える
3

次の回避策を使用してください。

#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
于 2015-04-05T20:40:51.447 に答える
2

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

于 2010-04-20T01:51:43.720 に答える
1

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

BSD Unixによって定義されたソケットAPIによって使用されるわずかに異なるアプローチは、に使用されるスタイルですstruct sockaddr

于 2010-04-20T01:57:04.653 に答える
1

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

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

于 2013-10-10T07:12:38.620 に答える
1

あなたが本当に何かを隠しbit-fieldたいのなら、それは良い解決策かもしれないと私は思いました。

struct person {
    unsigned long :64;
    char          *name;
    int           age;
};

struct wallet {
    char *currency;
    double balance;
};

構造体の最初のメンバーは、名前のないビットフィールドです。64-bit pointerこの場合に使用されます。完全に非表示になっており、構造体変数名でアクセスすることはできません

この構造体の最初の64ビットは使用されていないため、プライベートポインターとして使用できます。変数名の代わりにメモリアドレスでこのメンバーにアクセスできます。

void init_person(struct person* p, struct wallet* w) {
    *(unsigned long *)p = (unsigned long)w;
    // now the first 64-bit of person is a pointer of wallet
}

struct wallet* get_wallet(struct person* p) {
    return (struct wallet*)*(unsigned long *)p;
}

私のIntelMacでテストされた小さな実用的な例:

//
// Created by Rieon Ke on 2020/7/6.
//

#include <stdlib.h>
#include <string.h>
#include <assert.h>


#if __x86_64__ || __LP64__
#define PRIVATE_SET(obj, val) *(unsigned long *) obj = (unsigned long) val;
#define PRIVATE_GET(obj, type) (type)*(unsigned long *) obj;
#define PRIVATE_POINTER unsigned long:64
#else
#define PRIVATE_SET(obj, val) *(unsigned int *) obj = (unsigned int) val;
#define PRIVATE_GET(obj, type) (type)*(unsigned int *) obj;
#define PRIVATE_POINTER unsigned int:32
#endif

struct person {
    PRIVATE_POINTER;
    char *name;
    int age;
};

struct wallet {
    char *currency;
    double balance;
};

int main() {

    struct wallet w;
    w.currency = strdup("$$");
    w.balance = 99.9;

    struct person p;
    PRIVATE_SET(&p, &w) //set private member

    p.name = strdup("JOHN");
    p.age = 18;

    struct wallet *pw = PRIVATE_GET(&p, struct wallet*) //get private member

    assert(strcmp(pw->currency, "$$") == 0);
    assert(pw->balance == 99.9);

    free(w.currency);
    free(p.name);

    return 0;
}
于 2020-07-06T05:32:33.857 に答える
0

呼び出し元のコードがにキャストバックできることを考えると、あまりプライベートではありません(SomeStructSource *)。また、別のパブリックメンバーを追加したい場合はどうなりますか?バイナリ互換性を破る必要があります。

#include編集:それが.cファイルにあることを見逃しましたが、クライアントがそれをコピーしたり、場合によっては.cファイルを直接コピーしたりすることを妨げるものは何もありません。

于 2010-04-20T01:43:52.057 に答える
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;
}
于 2014-04-02T21:07:15.610 に答える
0

ここでは、匿名の構造体を使用できます。

#ifndef MYSTRUCT_H
#define MYSTRUCT_H

typedef struct {
  int i;
  struct {
    int j;
  } MYSTRUCT_PRIVATE;

  // NOTE: Avoid putting public members after private
  int k;
} MyStruct;

void test_mystruct();

#endif

プライベートメンバーにアクセスする必要があるファイルでは、MYSTRUCT_PRIVATEこのヘッダーを含める前に空のトークンとして定義します。これらのファイルでは、プライベートメンバーは匿名の構造体にあり、を使用してアクセスできますm.jが、他のすべての場所では、を使用してのみアクセスできますm.MYSTRUCT_PRIVATE.j

#define MYSTRUCT_PRIVATE
#include "mystruct.h"

void test_mystruct() {
  // Can access .j without MYSTRUCT_PRIVATE in both
  // initializer and dot operator.
  MyStruct m = { .i = 10, .j = 20, .k = 30 };
  m.j = 20;
}
#include <stdio.h>
#include "mystruct.h"

int main() {
  // You can declare structs and, if you jump through
  // a small hoop, access private members
  MyStruct m = { .i = 10, .k = 30 };
  m.MYSTRUCT_PRIVATE.j = 20;

  // This will not work
  //MyStruct m2 = { .i = 10, .j = 20, .k = 30 };

  // But this WILL work, be careful
  MyStruct m3 = { 10, 20, 30 };

  test_mystruct();

  return 0;
}

パブリックメンバーをプライベートメンバーの後に置くことはお勧めしません。withなどのメンバー指定子なしで構造体を初期化しても、{ 10, 20, 30 }プライベートメンバーを初期化できます。プライベートメンバーの数が変更された場合、これにより、メンバー指定子のないすべての初期化子もサイレントに中断されます。これを回避するには、常にメンバー指定子を使用するのが最善です。

C ++のように自動コンストラクターがないため、構造体、特にプライベートメンバーをゼロ初期化するように設計する必要があります。メンバーが0に初期化されている限り、初期化関数がなくてもメンバーが無効な状態のままになることはありません。メンバー指定子の初期化を除いて、単純に初期化する{ 0 }ことは安全であるように設計されるべきです。

私が見つけた唯一の欠点は、これがデバッガーやコード補完などを混乱させることです。通常、あるタイプのメンバーのセットが1つのファイルにあり、別のセットが別のファイルにある場合、それらは気に入らないのです。

于 2022-02-16T21:40:42.023 に答える