Давайте поговорим о полном инженерном решении, которое считалось лучшей практикой в старину.
Проблема со структурами заключается в том, что все общедоступно, поэтому данные не прячутся.
Мы можем это исправить.
Вы создаете два заголовочных файла. Одним из них является «публичный» заголовочный файл, используемый клиентами вашего кода. Содержит такие определения:
typedef struct t_ProcessStruct *t_ProcessHandle;
extern t_ProcessHandle NewProcess();
extern void DisposeProcess(t_ProcessHandle handle);
typedef struct t_PermissionsStruct *t_PermissionsHandle;
extern t_PermissionsHandle NewPermissions();
extern void DisposePermissions(t_PermissionsHandle handle);
extern void SetProcessPermissions(t_ProcessHandle proc, t_PermissionsHandle perm);
затем вы создаете частный заголовочный файл, который содержит такие определения:
typedef void (*fDisposeFunction)(void *memoryBlock);
typedef struct {
fDisposeFunction _dispose;
} t_DisposableStruct;
typedef struct {
t_DisposableStruct_disposer; /* must be first */
PID _pid;
/* etc */
} t_ProcessStruct;
typedef struct {
t_DisposableStruct_disposer; /* must be first */
PERM_FLAGS _flags;
/* etc */
} t_PermissionsStruct;
и тогда в своей реализации вы можете сделать что-то вроде этого:
static void DisposeMallocBlock(void *process) { if (process) free(process); }
static void *NewMallocedDisposer(size_t size)
{
assert(size > sizeof(t_DisposableStruct);
t_DisposableStruct *disp = (t_DisposableStruct *)malloc(size);
if (disp) {
disp->_dispose = DisposeMallocBlock;
}
return disp;
}
static void DisposeUsingDisposer(t_DisposableStruct *ds)
{
assert(ds);
ds->_dispose(ds);
}
t_ProcessHandle NewProcess()
{
t_ProcessHandle proc = (t_ProcessHandle)NewMallocedDisposer(sizeof(t_ProcessStruct));
if (proc) {
proc->PID = NextPID(); /* etc */
}
return proc;
}
void DisposeProcess(t_ProcessHandle proc)
{
DisposeUsingDisposer(&(proc->_disposer));
}
Что происходит, так это то, что вы делаете предварительные объявления для своих структур в ваших публичных заголовочных файлах. Теперь ваши структуры непрозрачны, что означает, что клиенты не могут с ними работать. Затем в полное объявление вы добавляете деструктор в начало каждой структуры, которую вы можете вызывать в общем. Вы можете использовать один и тот же распределитель malloc для каждой и той же функции dispose. Вы делаете общедоступными функции set / get для элементов, которые вы хотите раскрыть.
Внезапно ваш код становится намного более вменяемым. Вы можете получить структуры только от распределителей или функций, которые вызывают распределители, что означает, что вы можете инициализировать узкое место. Вы строите деструкторы, чтобы объект мог быть уничтожен. И вы идете. Кстати, лучшим именем, чем t_DisposableStruct, может быть t_vTableStruct, потому что это именно так. Теперь вы можете создавать виртуальное наследование, используя vTableStruct, который является указателем на функцию. Вы также можете делать вещи, которые вы не можете делать на чистом языке (как правило), например, изменять элементы выбора виртуальной таблицы на лету.
Важным моментом является то, что является инженерным шаблоном для создания конструкций, безопасных и инициализируемых.