この記事のポイント

  • 生配列利用の設計負債をレビューアー視点で読み解ける
  • vector移行判断基準を具体的に整理
  • 配列移行時に発生しがちな設計ミスをレビュー指摘できる

そもそも配列からvector移行とは

C++では昔から以下のように生配列が多用されてきました。

ApiRequestLog logs[10];          // 固定長ローカル配列
ApiRequestLog* logs = new ApiRequestLog[10];  // 動的確保配列

これらはすべてスコープ依存性・解放責任・例外安全性において脆弱です。
特に以下の問題が生じます。

  • delete[]忘れ
  • バッファオーバーラン
  • サイズ管理の分散
  • 例外時リーク

C++では標準コンテナ std::vector を使うことでこれらの問題を構造的に排除できます。

基本原則
  • 配列は原則std::vectorに置き換え可能
  • 生配列は低レベルAPI境界部限定使用
  • new/delete[]は極力排除する

レビューアーは「vector移行の余地が残されていないか」を読み取る役割を担います。

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

生配列をvectorへ移行しないまま放置すると以下が積み重なります。

  • 保守工数の増大
  • バグ温床化(未定義動作・境界違反)
  • 例外安全の崩壊
  • テスト容易性の悪化

レビュー段階で移行提案することがレビューアーの責務になります。

レビューアー視点

  • 固定長配列使用箇所を洗い出す
  • new/delete[]依存箇所を読み取る
  • 動的配列操作箇所を確認
  • vector移行で副作用が発生しないか評価
  • 移行障害ポイント(ポインタAPI等)を整理

開発者視点

  • new/delete[]封じ込め
  • vectorスコープ依存管理の活用
  • push_back/resizeによる動的成長吸収
  • 高水準APIへの移行設計

良い実装例

想定:APIリクエストログ配列管理

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

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

class LogBuffer {
public:
    explicit LogBuffer(size_t capacity)
        : logs_(capacity) {}

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

    void add(const ApiRequestLog& entry) {
        logs_.push_back(entry);
    }

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

良いポイント

  • delete[]完全排除
  • スコープ自動解放
  • 境界越え検査提供(at()活用可能)
  • push_backで柔軟成長

レビュー観点

  • 固定長生配列はvectorに置き換えられているか
  • 動的new/delete[]が封じ込められているか
  • size管理責務がvectorに吸収されているか
  • 例外安全性が構造保証できているか
  • API利用側の所有権負担が軽減しているか

良くない実装例: ケース1(固定長ローカル配列)

問題例①
void process() {
    ApiRequestLog logs[10];

    logs[0].requestId = 1;
    logs[0].endpoint = "/api/items";
}
@Reviewer
固定長配列はstd::vectorへ移行してください。スコープ安全性は得られますが柔軟性・サイズ管理負荷が残ります。vector管理に統一しましょう。

問題点

  • サイズ固定の設計硬直
  • 成長対応困難
  • 境界越え検出困難

改善例

改善例①
std::vector<ApiRequestLog> logs;
logs.push_back({1, "/api/items", "127.0.0.1", 200, std::time(nullptr)});

良くない実装例: ケース2(new/delete[]動的配列)

問題例②
ApiRequestLog* logs = new ApiRequestLog[10];
logs[0].requestId = 1;
// 中略
delete[] logs;
@Reviewer
動的配列はstd::vectorで管理してください。delete[]責務をvectorに吸収し、例外安全性も確保しましょう。

問題点

  • delete[]忘れ誘発
  • スコープ外責任散乱
  • 例外発生時リークリスク

改善例

改善例②
std::vector<ApiRequestLog> logs(10);
logs[0].requestId = 1;

良くない実装例: ケース3(ポインタAPI移行困難放置)

問題例③
void save(ApiRequestLog* logs, size_t count) {
    for (size_t i = 0; i < count; ++i) {
        std::cout << logs[i].requestId << std::endl;
    }
}
@Reviewer
API契約もvector受取にリファクタリングしてください。ポインタ+サイズ契約はバラけやすく、vector受取なら統一責務になります。

改善例

改善例③
void save(const std::vector<ApiRequestLog>& logs) {
    for (const auto& entry : logs) {
        std::cout << entry.requestId << std::endl;
    }
}

観点チェックリスト


まとめ

生配列 → vector移行レビューは
「責務分散設計 → 責務吸収設計」 への進化レビューです。

レビューアーは

  • どこに生配列が残っているか?
  • 呼び出し側のサイズ責務が残っていないか?
  • 例外安全性は保証されているか?

を読み取り、設計の安全領域へ移行誘導する視点が重要になります。

UML Diagram