Скрытие членов в структуре C - PullRequest
57 голосов
/ 20 апреля 2010

Я читал об ООП в C, но мне никогда не нравилось, что у вас не может быть личных данных, как в C ++. Но потом мне пришло в голову, что вы можете создать 2 структуры. Один определяется в заголовочном файле, а другой - в исходном файле.

// =========================================
// 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;
}

Отсюда вы можете просто привести одну структуру к другой. Это считается плохой практикой? Или это часто делается?

Ответы [ 13 ]

46 голосов
/ 20 апреля 2010

sizeof(SomeStruct) != sizeof(SomeStructSource). Это заставит заставить кого-то найти вас и однажды убить вас.

31 голосов
/ 20 апреля 2010

Лично мне бы больше понравилось:

typedef struct {
  int _public_member;
  /*I know you wont listen, but don't ever touch this member.*/
  int _private_member;
} SomeStructSource;

В конце концов, это C, если люди хотят облажаться, им нужно разрешить - не нужно ничего скрывать, кроме:

Если вам нужно, чтобы ABI / API был совместимым, есть два подхода, которые более распространены из того, что я видел.

  • Не предоставляйте своим клиентам доступ к структуре, не давайте им непрозрачный дескриптор (пустота * с красивым именем), предоставляйте функции init / destroy и accessor для всего. Это гарантирует, что вы можете изменить структура даже без перекомпиляции клиентов, если вы пишете библиотеку.

  • предоставляет непрозрачный дескриптор как часть вашей структуры, который вы можете размещать по своему усмотрению. Этот подход даже используется в C ++ для обеспечения совместимости ABI.

1018 * например *

 struct SomeStruct {
  int member;
  void* internals; //allocate this to your private struct
 };
25 голосов
/ 20 апреля 2010

У вас почти есть это, но вы не зашли достаточно далеко.

В заголовке:

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, и вы можете безнаказанно изменять его, добавляя и удаляя элементы по желанию, даже без необходимости перекомпиляции потребителей.Кроме того, они не могут «случайно» членов munge напрямую или размещать SomeStruct в стеке.Это, конечно, также можно рассматривать как недостаток.

15 голосов
/ 11 марта 2013

Я не рекомендую использовать шаблон публичной структуры. Правильный шаблон проектирования для ООП в C состоит в том, чтобы предоставлять функции для доступа к любым данным, никогда не позволяя публичный доступ к данным. Данные класса должны быть объявлены в источнике, чтобы быть частными, и ссылаться на них в прямом направлении, где 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 */
}
9 голосов
/ 20 апреля 2010

Никогда не делай этого. Если ваш API поддерживает что-то, что принимает SomeStruct в качестве параметра (что я ожидаю, что это делает), то они могли бы выделить один в стек и передать его. Вы получите серьезные ошибки при попытке получить доступ к приватному члену, начиная с компилятора Выделение для клиентского класса не содержит места для него.

Классический способ скрыть элементы в структуре - сделать ее недействительной *. Это в основном дескриптор / cookie, о котором знают только ваши файлы реализации. Практически каждая библиотека C делает это для личных данных.

7 голосов
/ 20 апреля 2010

Нечто похожее на предложенный вами метод действительно иногда используется (например, посмотрите различные варианты struct sockaddr* в API сокетов BSD), но его практически невозможно использовать без нарушения строгих правил псевдонимов 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;
}
4 голосов
/ 20 апреля 2010

Я бы написал скрытую структуру и сослался бы на нее с помощью указателя в публичной структуре. Например, ваш .h может иметь:

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

И твой .c:

typedef struct {
    int c, d;
} private_t;

Очевидно, что он не защищает от арифметики с указателями и добавляет немного накладных расходов на распределение / освобождение, но я думаю, что это выходит за рамки вопроса.

3 голосов
/ 05 апреля 2015

Используйте следующий обходной путь:

#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
2 голосов
/ 20 апреля 2010

Есть лучшие способы сделать это, например, использовать указатель void * на приватную структуру в публичной структуре. То, как вы делаете это, вы обманываете компилятор.

1 голос
/ 20 апреля 2010

Этот подход действителен, полезен, стандарт C.

Несколько иной подход, используемый API сокетов, который был определен в BSD Unix, - это стиль, используемый для struct sockaddr.

...