解放のお悩みをTemplateパターンで解決!

に公開

はじめに

こんにちは、42TokyoでC言語を用いた実践的なCSを学んでいる うらっしゅです!

C言語でのプログラミングにおいて、エンジニアが頻繁に直面する問題のひとつが、動的に確保したリソースの解放漏れではないでしょうか?
特に malloc を使った場面では、メモリ確保の成功可否を判定し、失敗時はそれまでの割り当てを手動で解放しなければなりません。
このようなコードは、リソースの獲得・解放処理が本来の目的であるロジックを囲い込んでしまい、コードの可読性や保守性を大きく損なう傾向があります。

複数のリソースを扱う処理では、途中でエラーが発生したときに、どのように安全に後始末(clean-up)を行うか、しばしば煩雑な制御構造が必要になります。

そこで本記事では、この問題を解決するアプローチとして、オブジェクト指向設計で知られる テンプレートメソッドパターン を、C言語流に応用する方法をご紹介します。

対象読者

  • C言語で mallocfopen を使った処理に慣れてきた初中級者
  • メモリリークやリソース解放漏れに課題感を持っている方
  • 既存コードが gotoif のネストで煩雑になっていることに悩んでいる方
  • 設計・実装の観点から「きれいな後片付け」の方法に関心がある方
  • オブジェクト指向の設計パターンを関数型/手続き型言語で応用してみたい方

このコードはリークしない...?

まずは、以下のコードを見てみてください。
fopen 関数を使った、典型的な「ファイルの内容を読み取って加工する」処理の一部です。

ファイルの内容を出力する関数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int read_and_process_file(const char *filename) {
    FILE *fd = fopen(filename, "r");
    if (fd == NULL) {
        fprintf(stderr, "ファイルを開けません: %s\n", filename);
        return -1;
    }

    char *line_buf = malloc(1024);
    if (!line_buf) {
        fprintf(stderr, "メモリ確保に失敗しました。\n");
        fclose(fd);
        return -1;
    }

    if (fgets(line_buf, 1024, fd) == NULL) {
        fprintf(stderr, "ファイルの読み込みに失敗しました。\n");

        return -1;
    }

    // 読み込んだ`line_buf`を加工する処理
    // ~~~~~~複数行~~~~~~~~

    fclose(fd);
    free(line_buf);
    return 0;
}

お分かりでしょうか...?

そう、fgets で読み込みに失敗した場合、fclose(fd) が呼ばれないまま return してしまっています。
ファイルディスクリプタ(fd)が開きっぱなしになってしまい、これではリーク確定です。

仕方ない、fclose を足すかぁ
if (fgets(line_buf, 1024, fd) == NULL) {
    fprintf(stderr, "ファイルの読み込みに失敗しました。\n");
    free(line_buf);
    fclose(fd);
    return -1;
}

確かに、これでファイルディスクリプタの解放はできて、リークは防げるようになりました。
でも……ちょっと気持ち悪さ、ありませんか?

「メモリも手動で確保してるし、エラー箇所が増えるたびに freefclose を追加するの面倒だなぁ…」
「解放コードがバラバラに散ってて、うっかり漏れそう」
そんな感覚、経験したことある方も多いのではないでしょうか?

そして、ここではまだたった2つのリソースしか扱っていません。
もっと複雑な処理になったとき、果たしてミスなく解放できるでしょうか?

Tamplateパターンを適用すると

では、Templateパターンを適用した場合をみてみましょう

Templateパターン使った場合
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// テンプレート関数(共通のリソース管理を行う)
int read_file(const char *p_file_name, int (*processor)(FILE *fp)) {
    FILE *fp = fopen(p_file_name, "r");
    if (fp == NULL) {
        fprintf(stderr, "ファイルを開けません: %s\n", p_file_name);
        return -1;
    }

    // 具体的な処理を委譲
    int return_value = processor(fp);

    fclose(fp);  // 解放はテンプレート関数側で一括
    return return_value;
}

// 呼び出し側で定義する処理(具体的な加工処理)
int process_file(FILE *fp) {
    char *line_buf = malloc(1024);
    if (!line_buf) {
        fprintf(stderr, "メモリ確保に失敗しました。\n");
        return -1;
    }

    if (fgets(line_buf, 1024, fp) == NULL) {
        fprintf(stderr, "ファイルの読み込みに失敗しました。\n");
        free(line_buf);
        return -1;
    }

    // 読み込んだ`line_buf`を加工する処理
    // ~~~~~~複数行~~~~~~~~

    // 解放
    free(line_buf);
    return 0;
}

int main(void) {
    // ここで`read_file`の関数ポインタに`process_file`で注入を行っている。
    if (read_file("example.txt", process_file) != 0) {
        fprintf(stderr, "ファイル処理に失敗しました。\n");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

🧠 なぜこれがTemplate Methodパターン?

  • read_file が「テンプレート関数」にあたり、共通化された処理の枠組みfopen → 処理 → fcloseを提供します。
  • process_file が「具象メソッド」として、可変的な処理の内容を関数ポインタで差し込んでいる構成です。
  • main 側からは、read_file を使うだけで、安全にリソースを開放できる設計になっています。

✅ メリット

  • fcloseの書き忘れなし
  • 処理ごとに fopen/fclose を書かなくて済むため、可読性が上がる
  • リソース数が増えても 管理の責任を分離できる
  • 例外のないC言語でも、構造化されたエラーハンドリングを模倣できる

まとめ:Templateパターンでリソース解放の漏れを防ぐ

C言語では、リソースの確保と解放を手動で管理しなければならず、メモリリークやファイルディスクリプタの閉じ忘れが起きやすい環境です。
特に複数のリソースを扱う処理では、エラー発生時のクリーンアップコードが煩雑になりがちです。

この問題を解決する1つのアプローチとして、Template Methodパターンの考え方を応用することで、共通の処理(リソースの獲得と解放)をテンプレート関数側に集約し、可変な処理(実際の業務ロジック)を関数ポインタで外部から注入する構成にできます。

この設計によって、

  • fclose や free の書き忘れを防ぎ
  • 本来の目的に集中できる可読性の高いコードが書け
  • リソース管理の責務を関数レベルで分離できる

という明快なメリットが得られます。
Templateパターンでリソース解放の漏れ問題を全て防げるわけではありませんが、「fopen + fclose」「malloc + free」のような定型的な取得・解放セットを安全に処理したい場面では非常に有効なので、試してみてください。

適材適所でこの設計パターンを使えると、C言語でも驚くほど美しく、安全なコードが書けるようになるでしょう。

Discussion