Я читал об ООП в C, но мне никогда не нравилось, как у Вас не может быть частных элементов данных как Вы, может в C++. Но затем это прибыло по моему мнению, что Вы могли создать 2 структуры. Каждый определяется в заголовочном файле, и другой определяется в исходном файле.
// =========================================
// in somestruct.h
typedef struct {
int _public_member;
} SomeStruct;
// =========================================
// in somestruct.c
#include "somestruct.h"
typedef struct {
int _public_member;
int _private_member;
} SomeStructSource;
SomeStruct *SomeStruct_Create()
{
SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource));
p->_private_member = 42;
return (SomeStruct *)p;
}
Отсюда можно просто бросить одну структуру к другому. Это считают плохой практикой? Или это часто делается?
Лично мне больше нравится:
typedef struct {
int _public_member;
/*I know you wont listen, but don't ever touch this member.*/
int _private_member;
} SomeStructSource;
В конце концов, это C, если люди хотят облажаться, им следует разрешить - не нужно чтобы скрыть вещи, кроме:
Если вам нужно сохранить совместимость с ABI / API, есть 2 подхода, которые более распространены из того, что я видел.
Не давайте своим клиентам доступ к структуре, дайте им непрозрачный дескриптор (void * с красивым именем), предоставьте функции инициализации / уничтожения и доступа для всего. Это гарантирует, что вы можете изменить структуру, даже не перекомпилируя клиентов, если вы пишете библиотеку.
предоставляют непрозрачный дескриптор как часть вашей структуры, которую вы можете размещать как угодно. Этот подход используется даже в C ++ для обеспечения совместимости с ABI.
например
struct SomeStruct {
int member;
void* internals; //allocate this to your private struct
};
Я бы написал скрытую структуру и ссылался на нее, используя указатель в открытой структуре. Например, ваш .h мог бы иметь:
typedef struct {
int a, b;
void *private;
} public_t;
А ваш .c:
typedef struct {
int c, d;
} private_t;
Он явно не защищает от арифметики указателей и добавляет немного накладных расходов на выделение / освобождение, но я полагаю, что это выходит за рамки вопрос.
Что-то похожее на предложенный вами метод действительно иногда используется (например, см. Различные варианты struct sockaddr *
в API сокетов BSD), но его практически невозможно использовать, не нарушая строгих правил C99. правила наложения имен.
Однако вы можете сделать это безопасно :
somestruct.h
:
struct SomeStructPrivate; /* Opaque type */
typedef struct {
int _public_member;
struct SomeStructPrivate *private;
} SomeStruct;
somestruct.c
:
#include "somestruct.h"
struct SomeStructPrivate {
int _member;
};
SomeStruct *SomeStruct_Create()
{
SomeStruct *p = malloc(sizeof *p);
p->private = malloc(sizeof *p->private);
p->private->_member = 0xWHATEVER;
return p;
}
У вас почти есть это, но вы еще не зашли достаточно далеко.
В заголовке:
struct SomeStruct;
typedef struct SomeStruct *SomeThing;
SomeThing create_some_thing();
destroy_some_thing(SomeThing thing);
int get_public_member_some_thing(SomeThing thing);
void set_public_member_some_thing(SomeThing thing, int value);
В .c:
struct SomeStruct {
int public_member;
int private_member;
};
SomeThing create_some_thing()
{
SomeThing thing = malloc(sizeof(*thing));
thing->public_member = 0;
thing->private_member = 0;
return thing;
}
... etc ...
Дело в том, что теперь потребители не имеют нет сведений о внутреннем устройстве SomeStruct, и вы можете безнаказанно изменять его, добавляя и удаление участников по желанию, даже без необходимости перекомпиляции потребителям. Они также не могут «случайно» изменить участников напрямую или разместить SomeStruct в стеке. Это, конечно, тоже можно рассматривать как недостаток.
Никогда этого не делайте.Если ваш API поддерживает все, что принимает SomeStruct в качестве параметра (чего я ожидаю), они могут выделить один в стеке и передать его. Вы получите серьезные ошибки, пытаясь получить доступ к закрытому члену, поскольку тот, который является компилятором выделяет для клиентского класса, не содержит для него места.
Классический способ скрыть члены в структуре - сделать ее пустой *. По сути, это дескриптор / файл cookie, о котором знают только ваши файлы реализации. Практически каждая библиотека C делает это для частных данных.
Не очень конфиденциально, учитывая, что вызывающий код может привести обратно к (SomeStructSource *)
. Кроме того, что происходит, когда вы хотите добавить еще одного публичного участника? Вам придется нарушить двоичную совместимость.
РЕДАКТИРОВАТЬ: Я пропустил, что это было в файле .c, но на самом деле ничто не мешает клиенту скопировать его или, возможно, даже #include
напрямую вставить файл .c.
Есть более эффективные способы сделать это, например, использовать указатель void *
на частную структуру в публичной структуре. То, как вы это делаете, вы обманываете компилятор.
Этот подход является допустимым, полезным, стандартным C.
Несколько иной подход, используемый API сокетов, который был определен в BSD Unix, - это стиль, используемый для структуры struct sockaddr
.
sizeof (SomeStruct)! = sizeof (SomeStructSource)
. Это заставит кого-нибудь найти вас и когда-нибудь убить.