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

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

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

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

Благодарю.

5 ответов

Решение

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

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, а также функции получения и установки, доступные для любых свойств, к которым вы хотите предоставить прямой доступ.

Концепция иногда используется в 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 не передается.

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

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

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

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

struct my_struct;

void some_function(struct my_struct *);

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

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

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

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

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

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

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

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

Другие вопросы по тегам