各タイプのアロケータとデアロケータを抽象化します。与えられた型定義
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()
する関数を呼び出すことに注意してください。また、失敗して NULL を返す場合は、メモリの割り当てを試みず、NULL を返すcopyStr()
ことにも注意してください。同様に、残りの構造体を解放する前に z のメモリを削除する関数を呼び出します。最後に、f の値を NULL に設定します。 copyStr()
newFoo
destroyFoo()
destroyFoo()
ここで重要なのは、メンバー要素にもメモリ管理が必要な場合、アロケーターとデアロケーターが責任を他の関数に委任することです。したがって、型が複雑になるにつれて、次のようにアロケーターを再利用できます。
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*
、正しい型を渡すようにしました。