Абстрагируйте распределители и деаллокаторы для каждого типа. Дано определение типа
typedef struct foo
{
int x;
double y;
char *z;
} Foo;
создать функцию распределителя
Foo *createFoo(int x, double y, char *z)
{
Foo *newFoo = NULL;
char *zcpy = copyStr(z);
if (zcpy)
{
newFoo = malloc(sizeof *newFoo);
if (newFoo)
{
newFoo->x = x;
newFoo->y = y;
newFoo->z = zcpy;
}
}
return newFoo;
}
функция копирования
Foo *copyFoo(Foo f)
{
Foo *newFoo = createFoo(f.x, f.y, f.z);
return newFoo;
}
и функция деллокатора
void destroyFoo(Foo **f)
{
deleteStr(&((*f)->z));
free(*f);
*f = NULL;
}
Обратите внимание, что createFoo()
в свою очередь вызывает функцию copyStr()
, которая отвечает за выделение памяти и копирование содержимого строки. Также обратите внимание, что если copyStr()
завершится неудачно и вернет NULL, newFoo
не будет пытаться выделить память и вернуть NULL. Точно так же destroyFoo()
вызовет функцию для удаления памяти для z перед тем, как освободить оставшуюся часть структуры. Наконец, destroyFoo()
устанавливает значение f в NULL.
Ключевым моментом здесь является то, что распределитель и освобождающий элемент делегируют ответственность другим функциям, если элементы-члены также требуют управления памятью. Таким образом, по мере того, как ваши типы усложняются, вы можете использовать эти распределители следующим образом:
typedef struct bar
{
Foo *f;
Bletch *b;
} Bar;
Bar *createBar(Foo f, Bletch b)
{
Bar *newBar = NULL;
Foo *fcpy = copyFoo(f);
Bletch *bcpy = copyBar(b);
if (fcpy && bcpy)
{
newBar = malloc(sizeof *newBar);
if (newBar)
{
newBar->f = fcpy;
newBar->b = bcpy;
}
}
else
{
free(fcpy);
free(bcpy);
}
return newBar;
}
Bar *copyBar(Bar b)
{
Bar *newBar = createBar(b.f, b.b);
return newBar;
}
void destroyBar(Bar **b)
{
destroyFoo(&((*b)->f));
destroyBletch(&((*b)->b));
free(*b);
*b = NULL;
}
Очевидно, в этом примере предполагается, что члены не имеют времени жизни вне своих контейнеров. Это не всегда так, и вам придется соответствующим образом разработать свой интерфейс. Тем не менее, это должно дать вам представление о том, что должно быть сделано.
Это позволяет вам распределять и освобождать память для объектов в последовательном, четко определенном порядке, что составляет 80% битвы при управлении памятью. Остальные 20% следят за тем, чтобы каждый вызов распределителя уравновешивался деаллокатором, который является действительно сложной частью.
редактировать
Изменены вызовы функций delete*
, так что я передаю нужные типы.