Подтвердить что ты не робот

Скрытие членов в 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;
}

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

4b9b3361

Ответ 1

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

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

В конце концов, если люди хотят испортить, им должно быть позволено - не нужно скрывать вещи, кроме:

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

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

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

например

 struct SomeStruct {
  int member;
  void* internals; //allocate this to your private struct
 };

Ответ 2

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

Ответ 3

У вас его почти нет, но он не прошел достаточно далеко.

В заголовке:

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 в стеке. Это, конечно, также можно рассматривать как недостаток.

Ответ 4

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

Ответ 5

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

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

Ответ 6

Что-то похожее на предложенный вами метод действительно используется иногда (например, см. различные варианты 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;
}

Ответ 7

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

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

И ваш .c:

typedef struct {
    int c, d;
} private_t;

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

Ответ 8

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

#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

Ответ 9

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

Ответ 10

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

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

Ответ 11

Не очень закрытый, учитывая, что вызывающий код может вернуться к (SomeStructSource *). Кроме того, что происходит, когда вы хотите добавить другого публичного участника? Вам придется нарушить двоичную совместимость.

EDIT: я пропустил, что он был в файле .c, но на самом деле ничего не мешает клиенту копировать его или, возможно, даже #include непосредственно в .c файле.

Ответ 12

Моим решением было бы предоставить только прототип внутренней структуры, а затем объявить определение в файле .c. Очень полезно показать C-интерфейс и использовать С++.

.h:

struct internal;

struct foo {
   int public_field;
   struct internal *_internal;
};

.c:

struct internal {
    int private_field; // could be a C++ class
};

Примечание. В этом случае переменная должна быть указателем, потому что компилятор не может знать размер внутренней структуры.

Ответ 13

Связано, хотя и не совсем скрывается.

Является условным отказом членов.

Обратите внимание, что это работает для 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;
}