この記事のポイント

  • delete/delete[]の責務分離設計をレビューアー視点で整理
  • 手動deleteの分散設計をレビュー段階で摘み取れる
  • スマートポインタ移行設計レビューの指導技術が学べる

そもそもdeleteとdelete[]とは

C++における動的メモリ管理で登場するのが以下の解放操作です。

  • delete → 単一オブジェクトを解放
  • delete[] → 配列オブジェクトを解放

どちらもヒープリソースの解放責任を担います。
newとdeleteは必ずペアとなるべきですが、実務では以下のような崩れたコードが多発します。

Foo* ptr = new Foo();  // 単一
delete ptr;

Foo* arr = new Foo[10];  // 配列
delete[] arr;

RAII設計未導入のコードほどdelete責務が散乱しがちです。
レビューアーは「解放責任がコード内に分散していないか」を読み解く必要があります。

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

delete責務がコード内で分散すると以下の設計事故を引き起こします。

  • 二重delete
  • delete忘れ(リーク)
  • delete[]/delete混用ミス(未定義動作)
  • 例外時の途中リソース破棄漏れ

レビュー段階でdelete管理構造を一元化させることが
設計の保守性・例外安全性を決定付ける重要観点になります。

レビューアー視点

  • delete位置が複数箇所に分散していないか
  • 配列用delete[]とdeleteが混在していないか
  • 所有権がスマートポインタ化できる構造か
  • コンストラクタ集中破棄モデルに寄せられるか
  • 例外発生時の自動解放が担保できているか

開発者視点

  • delete責務は1クラスに集約
  • コンストラクタ→デストラクタ集中設計
  • スマートポインタ化を優先検討
  • 配列new/delete[]は極力避ける

良い実装例

想定:APIログレコードの所有権集中モデル

良い設計例
struct ApiRequestLog {
    int requestId;
    std::string endpoint;
    std::string clientIp;
    int responseCode;
    time_t requestedAt;
};

class LogBuffer {
public:
    LogBuffer(size_t capacity)
        : size_(capacity), logs_(new ApiRequestLog[capacity]) {}

    ~LogBuffer() {
        delete[] logs_;
    }

    ApiRequestLog& operator[](size_t index) {
        return logs_[index];
    }

private:
    size_t size_;
    ApiRequestLog* logs_;
};

良いポイント

  • 配列delete[]責務をクラス内に集中
  • 呼び出し側は解放責任を持たない
  • 例外安全性が高い構造

レビュー観点

  • delete/delete[]責務が1クラスに集約されているか
  • 呼び出し側にdelete責務を転嫁していないか
  • スマートポインタ導入余地が残されていないか
  • 例外発生時の途中解放保証があるか
  • 配列管理をstd::vector等へ移行検討されているか

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

問題例①
class LogBuffer {
public:
    ApiRequestLog* create(size_t capacity) {
        return new ApiRequestLog[capacity];
    }

    void release(ApiRequestLog* logs) {
        delete[] logs;
    }
};

void handleRequest() {
    LogBuffer buffer;
    auto logs = buffer.create(10);

    // 中略

    buffer.release(logs);
}
@Reviewer
create/releaseモデルは責務分散を招きます。コンストラクタで配列確保し、デストラクタでdelete[]を呼び出すRAII設計に統一してください。

問題点

  • 呼び出し側にdelete責務転嫁
  • 例外安全性を損なう
  • API契約の複雑化

改善例

改善例①
class LogBuffer {
public:
    LogBuffer(size_t capacity)
        : size_(capacity), logs_(new ApiRequestLog[capacity]) {}

    ~LogBuffer() {
        delete[] logs_;
    }
};

良くない実装例: ケース2(delete/delete[]混用)

問題例②
ApiRequestLog* logs = new ApiRequestLog[10];
// 中略
delete logs;  // delete[]忘れ
@Reviewer
配列確保時はdelete[]を使用してください。もしくはvectorやunique_ptr配列管理へ移行し、解放責務を構造的に排除してください。

問題点

  • 配列をdeleteで解放 → 未定義動作
  • 静的解析で拾いにくい実害バグ発生

改善例

改善例②
delete[] logs;

もしくは完全自動化:

改善例②(推奨)
std::vector<ApiRequestLog> logs(10);

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

問題例③
ApiRequestLog* logs = new ApiRequestLog[10];
// 中略
delete[] logs;
@Reviewer
new/delete[]は可能な限り排除し、std::vectorまたはstd::unique_ptr<T[]>での自動管理に移行してください。RAII設計に統合しましょう。

改善例

改善例③(vector移行)
std::vector<ApiRequestLog> logs(10);

または

改善例③(unique_ptr配列)
std::unique_ptr<ApiRequestLog[]> logs(new ApiRequestLog[10]);

観点チェックリスト


まとめ

delete管理レビューは責務集中レビューそのものです。
レビューアーは常に

  • 解放責任が1クラスに閉じ込められているか?
  • 呼び出し側に解放責任を転嫁していないか?
  • new/deleteの露出が残っていないか?

を静的に読み解き、RAII構造への寄せ方を指導していくことが重要です。

UML Diagram