Канонический способ сделать это - использовать комбинацию непрозрачных указателей и открытых структур, а также распределителей, геттеров и сеттеров для приватных элементов. Примерно по этим направлениям:
foo.h
typedef struct Foo {
/* public elements */
} Foo;
Foo *new_Foo(void);
void Foo_something_opaque(Foo* foo);
foo.c
#include "foo.h"
typedef struct Private_Foo_ {
struct Foo foo;
/* private elements */
} Private_Foo_;
Foo *new_Foo(void)
{
Private_Foo_ *foo = malloc(sizeof(Private_Foo_));
/* initialize private and public elements */
return (Foo*) foo;
}
void Foo_something_opaque(Foo *foo)
{
Private_Foo_ *priv_foo = (Private_Foo_*) foo;
/* do something */
}
Это работает, поскольку C гарантирует, что адрес переменной структуры всегда равен адресу самого первого элемента структуры. Мы можем использовать это, чтобы иметь структуру Private_Foo_, содержащую публичный Foo в начале, выдавая указатели на все это, при этом модули компиляции не имеют доступа к определению структуры Private_Foo_, просто видя некоторую память без какого-либо контекста.
Следует отметить, что C ++ работает очень похоже за кулисами.
Обновление
Как указал KereekSB, он будет прерываться при использовании в массиве.
Я говорю: тогда не делайте Foo f[]
, каким бы заманчивым он ни был, но сделайте массивы указателей на Foo: Foo *f[]
.
Если вы действительно настаиваете на использовании его в массивах, сделайте следующее:
foo_private.h
typedef struct Private_Foo_ {
/* private elements */
} Private_Foo_;
static size_t Private_Foo_sizeof(void) { return sizeof(Private_Foo_); }
foo_private.h написан так, что его можно скомпилировать в объектный файл. Используйте некоторую вспомогательную программу, чтобы связать ее, и используйте результат Private_Foo_sizeof()
, чтобы сгенерировать фактический, зависящий от платформы foo.h из некоторого файла foo.h.in.
foo.h
#include
#define FOO_SIZEOF_PRIVATE_ELEMENTS <generated by preconfigure step>
typedef struct Foo_ {
/* public elements */
char reserved[FOO_SIZEOF_PRIVATE_ELEMENTS];
} Foo;
Foo *new_Foo(void);
void Foo_something_opaque(Foo* foo);
foo.c
#include "foo.h"
#include "foo_private.h"
Foo *new_Foo(void)
{
Foo *foo = malloc(sizeof(Foo));
/* initialize private and public elements */
return (Foo*) foo;
}
void Foo_something_opaque(Foo *foo)
{
Private_Foo_ *priv_foo = (Private_Foo_*) foo.reserved;
/* do something */
}
ИМХО, это действительно грязно. Теперь я фанат умных контейнеров (к сожалению, нет стандартной библиотеки контейнеров для C). В любом случае: в таком контейнере создается с помощью функции, подобной
Array *array_alloc(size_t sizeofElement, unsigned int elements);
void *array_at(Array *array, unsigned int index);
/* and all the other functions expected of arrays */
См. Libowfaw для примера такой реализации. Теперь для типа Foo было тривиально предоставить функцию
Array *Foo_array(unsigned int count);