この記事のポイント

  • new/delete使用箇所の集中管理設計をレビューアーが読み解く技術を整理
  • 解放責務の分散排除と所有権明示のレビュー観点を整理
  • スマートポインタ移行とRAII設計徹底のレビュー技術を解説

そもそもnew/delete集中管理とは

C++におけるnew/deleteは動的メモリ管理の最低レイヤです。
極めて柔軟ですが、以下のリスクを常に孕んでいます。

  • 解放漏れ(メモリリーク)
  • 二重delete(未定義動作)
  • 例外発生時の途中解放不能

設計の基本は
「new/deleteの露出を可能な限り狭いスコープに集中させる」
ことにあります。

集中管理の基本原則
  • new/deleteはコンストラクタ+デストラクタ内限定が基本形
  • 呼び出し側へ解放責任を転嫁しない
  • RAII徹底による自動解放に寄せる
  • 可能な限りスマートポインタ・標準コンテナに吸収

レビューアーはnew/delete露出箇所を読み取り、集中管理領域へ寄せ切れているか確認する役割を持ちます。

なぜこれをレビューするのか

new/deleteがコード全体に散乱すると以下の設計崩壊を誘発します。

  • メモリ解放責務が分散 → 管理不能化
  • 例外発生時リーク → 安全性破綻
  • テスト容易性低下
  • 読解コスト肥大化

レビュー段階で
「new/deleteがどこに現れているか」
を静的に全スコープで読み切る力がレビューアーの品質を左右します。

レビューアー視点

  • new箇所を完全に洗い出す
  • delete箇所が複数箇所に存在していないか確認
  • スマートポインタ移行可能性を確認
  • コンストラクタ集中に設計寄せできているか確認
  • 例外安全性を崩していないか評価

開発者視点

  • new/deleteは設計責務封じ込めの最上位に置く
  • スマートポインタ・コンテナへ極力移行
  • 解放責任はスコープ管理に寄せる
  • 例外安全性を自然保証できる構造に設計する

良い実装例

想定:APIリクエストログの集中管理型

良い設計例
#include <memory>
#include <vector>
#include <string>
#include <iostream>

struct ApiRequestLog {
    int requestId;
    std::string endpoint;
    std::string clientIp;
    int responseCode;
    time_t requestedAt;
};

class LogBuffer {
public:
    void add(std::unique_ptr<ApiRequestLog> logEntry) {
        logs_.push_back(std::move(logEntry));
    }

    void dump() const {
        for (const auto& entry : logs_) {
            std::cout << entry->requestId << std::endl;
        }
    }

private:
    std::vector<std::unique_ptr<ApiRequestLog>> logs_;
};

void process() {
    auto log = std::make_unique<ApiRequestLog>();
    log->requestId = 123;

    LogBuffer buffer;
    buffer.add(std::move(log));
}

良いポイント

  • new/delete完全封じ込み
  • 寿命責務集中
  • 例外安全自然保証
  • API契約明示化(所有権移譲)

レビュー観点

  • new呼び出し箇所を完全把握できるか
  • deleteが複数箇所に分散していないか
  • コンストラクタ集中に設計寄せできているか
  • スマートポインタ移行余地が残っていないか
  • 呼び出し側の解放責務が消滅しているか
  • 例外時も寿命保証されているか

良くない実装例: ケース1(delete責務分散)

問題例①
class LogBuffer {
public:
    void add(ApiRequestLog* logEntry) {
        logs_.push_back(logEntry);
    }

    void cleanup() {
        for (auto* entry : logs_) {
            delete entry;
        }
        logs_.clear();
    }

private:
    std::vector<ApiRequestLog*> logs_;
};
@Reviewer
delete責務がクラス内に分散しています。スマートポインタを導入し、所有権移譲をadd()引数型で明示してください。cleanup()も不要になります。

問題点

  • 解放責務分散
  • 保守コスト上昇
  • 例外時安全性不保証

改善例

改善例①
void add(std::unique_ptr<ApiRequestLog> logEntry) {
    logs_.push_back(std::move(logEntry));
}

良くない実装例: ケース2(コンストラクタ集中欠如)

問題例②
class Session {
public:
    void start() {
        log_ = new ApiRequestLog();
    }

    void end() {
        delete log_;
    }

private:
    ApiRequestLog* log_;
};
@Reviewer
new/deleteはコンストラクタとデストラクタに集中させてください。可能ならunique_ptr導入で自動解放に統合してください。

改善例

改善例②
class Session {
public:
    Session() : log_(std::make_unique<ApiRequestLog>()) {}

private:
    std::unique_ptr<ApiRequestLog> log_;
};

良くない実装例: ケース3(スマートポインタ未導入)

問題例③
ApiRequestLog* log = new ApiRequestLog();
// 処理略
delete log;
@Reviewer
new/deleteは基本的にコード露出を排除してください。スマートポインタまたはスコープ自動管理構造に統一移行してください。

改善例

改善例③
auto log = std::make_unique<ApiRequestLog>();

観点チェックリスト


まとめ

new/delete集中管理レビューは
「破棄責任を構造上読める設計に仕上げるレビュー」です。

レビューアーは常に

  • new呼び出しはどこに存在するか?
  • delete責務は集中管理されているか?
  • 自動解放設計に寄せ切れているか?

を静的に読み取り、RAII徹底 → 呼び出し側負荷ゼロ化設計へ誘導するのが役割です。

UML Diagram