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

Разделение структуры на частные и публичные разделы?

В С++ и Java структуры данных могут иметь области private, public и protected. Я хотел бы передать эту концепцию в программу на языке C, которую я пишу.

Существуют ли какие-либо идиомы для реализации частных или защищенных указателей и полей данных в C struct? Я знаю, что C struct являются общедоступными, я ищу идиому, чтобы скрыть некоторые детали реализации и заставить пользователей использовать открытый интерфейс.

Примечание. Язык выбран в магазине, поэтому я застрял в реализации объектно-ориентированных концепций на C.

Спасибо.

4b9b3361

Ответ 1

Как вы знаете, вы не можете этого сделать. Однако есть идиомы, которые позволят аналогичный эффект.

C позволит вам сделать что-то похожее на то, что называется "pimpl" идиомой в объектно-ориентированном дизайне. У вашей структуры может быть непрозрачный указатель на другую структуру, объявленную вперед, которая действует как личные данные структуры. Функции, которые работают с структурой, занимая место функций-членов, могут иметь полное определение для частного члена и могут использовать его, в то время как другие части кода не могут. Например:

В заголовке foo.h:

  struct FooPrivate;

  struct Foo {
     /* public: */
       int x; 
       double y;
     /* private: */
       struct FooPrivate* p;
  };

  extern struct Foo* Foo_Create(); /* "constructor" */

  extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */

В реализации foo.c:

  struct FooPrivate {
     int z;
  };

  struct Foo* Foo_Create()
  {
     struct Foo* foo = malloc(sizeof(Foo));

     foo->p = malloc(sizeof(FooPrivate));

     foo->x = 0;
     foo->y = 0;
     foo->p->z = 0;

     return foo;
  }

  void Foo_DoWhatever(struct Foo* foo) 
  {
      foo->p->z = 4; /* Can access "private" parts of foo */
  }

В программе:

  #include "foo.h"

  int main()
  {
      struct Foo* foo = Foo_Create();

      foo->x = 100; /* Can access "public" parts of foo */
      foo->p->z = 20; /* Error! FooPrivate is not fully declared here! */

      Foo_DoWhatever(foo); /* Can call "member" function */

      return 0;
  }

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

Или, альтернативно, если вы хотите, чтобы ваша структура не имела общих полей, вы могли бы сделать всю структуру непрозрачной и просто иметь заголовок как-то вроде

  struct Foo;

  extern struct Foo* Foo_Create(); /* "constructor" */

  extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */

С фактическим определением struct Foo в функциях foo.c и getter и setter, доступными для любых свойств, которые вы хотели бы предоставить для прямого доступа.

Ответ 2

Понятие, иногда используемое в C,

// lib.h
typedef struct {
  int publicInt;
  //...
  char * publicStr;
} Public;

Public * getPublic();
int function(Public * public);

// lib.c

typedef struct {
  Public public;
  int privateInt;
  // ...
  char * privateStr
} Private;

static Private * getPrivate();

Public * getPublic() { return (Public*) getPrivate(); }
int function(Public * public) {
  Private * private = (Private *) public;
  // ...
}

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

Если вы хотите, чтобы все ваши поля были приватными, это еще проще:

// lib2.h
typedef struct AllPrivate * Handle;
Handle getHandle();
int function2(Handle handle);

// lib2.c
struct AllPrivate { /* ... */ }

Файлы, которые #include lib2.h не будут жаловаться, поскольку мы используем только struct AllPrivate *, и все указатели имеют одинаковый размер, поэтому компилятору не нужно знать внутренности struct AllPrivate.

Чтобы сделать защищенный регион, вам просто нужно определить

// include/public.h
struct Public { /* ... */ }
struct Public * getPublic();
int somePublicFunction(struct Public *);

// dev/include/protected.h
struct Protected { struct Public public; /* ... */ }
struct Protected * getProtected();
int someProtectedFunction(struct Protected *);

// dev/src/private.c
struct Private { struct Protected protected; /* ... * /}
struct Public * getPublic() { return (struct Public *) getPrivate(); }
struct Public * getProtected() { return (struct Protected *) getPrivate(); }
int somePublicFunction(struct Public * public) { 
  struct Private private = (struct Private *) public;
  // ...
}
int someProtectedFunction(struct Protected * protected) { 
  struct Private private = (struct Private *) protected;
  // ...
}

Тогда просто нужно убедиться, что dev/include не передается.

Ответ 3

Для полей данных - просто не используйте их. Вы можете сделать некоторые трюки, как дать им сумасшедшие имена, чтобы препятствовать их использованию, но это не остановит людей. Единственный реальный способ сделать это - сделать еще одну частную структуру, к которой обращаются указатели void по вашим библиотечным функциям.

Для частных функций - используйте функции static. Поместите все ваши библиотечные функции в один файл C и объявите те, которые вы хотите быть закрытыми, как static и не помещайте их в файлы заголовков.

Ответ 4

Часто по соглашению, частный член имеет дополнительное подчеркивание в своем имени или что-то вроде _pri прилагается. Или, возможно, комментарий. Этот метод не выполняет проверку, основанную на компиляторах, чтобы удостовериться, что кто-то неправомерно обращается к этим полям, но служит предупреждением всем, кто читает объявление struct, что содержимое представляет собой детали реализации, и они не должны заглядывать или ткнуть в них.

Другим распространенным методом является раскрытие вашей структуры как неполного. Например, в вашем файле заголовка могут быть:

struct my_struct;

void some_function(struct my_struct *);

И в реализации или некотором внутреннем заголовке, недоступном для пользователей библиотеки, вы имеете:

struct my_struct
{
    /* Members of that struct */
};

Вы также можете делать подобные трюки с указателями void, которые будут приведены в нужное место в "private" части кода. Этот подход теряет определенную гибкость (например, вы не можете иметь экземпляр, выделенный в стеке типа undefined), но это может быть приемлемым.

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

Хотя, это вводит некоторую косвенность, что может повредить производительность. Есть некоторые (обычно не переносные, но будут работать на разумных компиляторах), которые можно использовать также:

struct public_struct
{
   int public_member;
   int public_member2;
   /* etc.. */
};

struct private_struct
{
   struct public_struct base_members;

   int private_member1;
   int private_member2;
};

void some_function(struct public_struct *obj)
{
   /* Hack alert! */
   struct private_struct *private = (struct private_struct*)obj;
}

Это также предполагает, что вы не можете хранить эти объекты в стеке или в статическом хранилище или получить размер во время компиляции.

Ответ 5

Мне жаль вас, потому что слияние различных концепций OO в C часто бывает как квадратная привязка в круглом отверстии. При этом GObject имеет поддержку публичных и частных членов, но это одна из моих наименее любимых архитектур на земле. Если вас не интересует незначительный удар производительности, вы можете сделать более простое решение - иметь вторичную структуру, заполненную частными членами, и иметь анонимный указатель на эту структуру из первичной (общедоступной) структуры.