Разделить структуру на частные и публичные разделы? - PullRequest
14 голосов
/ 29 сентября 2010

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

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

Спасибо.

Ответы [ 5 ]

26 голосов
/ 29 сентября 2010

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

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

9 голосов
/ 29 сентября 2010

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

2 голосов
/ 29 сентября 2010

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

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

1 голос
/ 29 сентября 2010

Часто, по соглашению, закрытый член имеет дополнительное подчеркивание в своем имени или что-то вроде _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;
}

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

0 голосов
/ 29 сентября 2010

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

...