依存関係によるメモリ管理

今更このご時世にC言語C++でなく)におけるメモリ管理の話なんぞしてもという気はするが、かれこれ数年ほど使ってきてわりと便利だったので公開してみる。

要求される仕様は以下のような感じ。

  1. 開放忘れを防ぎたい
  2. 後処理などの手間を軽減したい
  3. 単純で癖のない仕様
  4. 削除するタイミングは管理したい

そんなわけで、こんなのを使っている。

void *DP_Malloc(size_t size, void *owner);
void DP_Free(void *p);

第2パラメータのownerに同じくDP_Mallocで得られたポインタを渡すと、そいつが親になる。親がDP_Freeされると、子も自動でDP_Freeされる。もちろん、子だけをDP_Freeで開放してもよいし、その場合は管理下から外れる。

Field *field = DP_Malloc(sizeof(Field), null);
Monster *monsterA = DP_Malloc(sizeof(Monster), field);
Monster *monsterB = DP_Malloc(sizeof(Monster), field);
monsterA->texture = DP_Malloc(sizeof(Texture), monsterA);
monsterB->texture = DP_Malloc(sizeof(Texture), monsterB);

DP_Free(monsterA); // monsterA->textureも一緒に開放される
DP_Free(field); // 残りのmonsterBとそのtextureが開放される

こんな感じ。ownerにNULLを指定したポインタは、自分でDP_Freeする。

各環境のAPIなどから得られたポインタをこの依存関係に組み入れることもできる。それには以下の関数を使う。

typedef void (*DP_Func)(void*);
void *DP_Link(void *p, DP_Func del_func, void *owner);

こんな感じで使う。

static void *FCloseFunc(void *p) {
    fclose(p);
}
FILE *fp = DP_Link(fopen("boo.dat", "rb"), FCloseFunc, null);
FileHeader *header = DP_Malloc(sizeof(FileHeader), fp);

/* ヘッダのデータを読んでなんかしたりする */

DP_Free(fp); // headerも開放される&freeの代わりにFCloseFuncが呼ばれる

削除の前にやりたい処理がある場合は以下で関数を登録する。デストラクタ的な。

void *DP_SetFinalize(void *p, DP_Func final_func)

ざっとこんな感じ。ソースは以下。