Разделить структуру на частные и публичные разделы?
В 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
имеет поддержку для публичных и частных членов, но это одна из моих наименее любимых архитектур на земле. Если вас не интересует незначительное снижение производительности, вы можете сделать более простое решение - иметь вторичную структуру, которая заполнена частными членами, и иметь анонимный указатель на эту структуру из первичной (публичной) структуры.