この記事のポイント

  • 配列からvectorへ移行する設計意図をレビューアー視点で整理
  • 生配列利用の危険箇所をレビューで特定する力を養う
  • 安全性・所有権・ライフサイクル設計に基づく統一方針を学べる

そもそも「配列→vector移行方針」とは

C++には以下の2つの配列表現が混在してきた歴史があります。

  • 生配列(C配列)
    • int arr[10];
    • int* arr = new int[10];
  • vector(C++ STL配列コンテナ)
    • std::vector<int> arr;

C++標準化の過程で、安全性・柔軟性の観点からvector移行が強く推奨されるようになりました。

項目 生配列 vector
サイズ管理 開発者責任 自動管理
所有権明示 曖昧 明確
メモリ解放 手動 自動
バウンダリチェック なし at()で可能
ムーブ可能性 難しい 標準対応
標準アルゴリズム適合性 低い 高い
例外安全性 脆弱 強い

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

レビューアー視点

配列→vector移行では、以下の設計論点が発生します。

  • 所有権モデルの統一
    → 関数間引き渡しで生ポインタが残っていないか

  • ライフサイクルの安定化
    → 動的確保・解放責任がvectorに移譲されているか

  • 例外安全性の向上
    → new/deleteペアが消失しているか

  • 境界チェック責任の移譲
    → at()・range-based for活用が進んでいるか

  • API契約の可読化
    → サイズ情報の別引数渡しが消えているか

開発者視点

  • 生配列→vector移行が中途半端
  • vectorを値渡し設計に抵抗感
  • resize/reserveの使い方不明確
  • pointer演算癖が抜けない
  • vectorのイテレータ範囲型に不慣れ

レビューはこれらの「配列思考の残滓」を読み取る役割を持ちます。

良い実装例

ユースケース:APIリクエストの固定長バッファ処理

良い実装例:vectorでの管理統一
#include <vector>
#include <string>
#include <cstdint>

struct ApiRequestLog {
    std::string requestId;
    int responseCode;
    int64_t requestedAt;
};

class RequestBatch {
public:
    explicit RequestBatch(size_t capacity) {
        logs_.reserve(capacity);
    }

    void add(const ApiRequestLog& log) {
        if (logs_.size() < logs_.capacity()) {
            logs_.push_back(log);
        }
    }

    const std::vector<ApiRequestLog>& getLogs() const {
        return logs_;
    }

private:
    std::vector<ApiRequestLog> logs_;
};
  • サイズ管理責任がvectorに吸収
  • 境界チェックが明示化
  • reserve活用による初期容量最適化
  • delete解放責任消滅

レビュー観点

  • 生配列の残留が完全に消えているか
  • サイズ情報の別管理がvectorサイズに統合されているか
  • push_back安全設計が取れているか
  • reserve適用が適切に行われているか
  • API間のvector値渡し抵抗を解消できているか

良くない実装例: ケース1

以下は生配列をそのまま引きずっているケースです。

生配列残留例
#include <string>
#include <cstdint>

struct ApiRequestLog {
    std::string requestId;
    int responseCode;
    int64_t requestedAt;
};

class RequestBatch {
public:
    RequestBatch() : size_(0) {}

    void add(const ApiRequestLog& log) {
        if (size_ < 100) {
            logs_[size_] = log;
            ++size_;
        }
    }

    const ApiRequestLog* getLogs() const {
        return logs_;
    }

    size_t size() const {
        return size_;
    }

private:
    ApiRequestLog logs_[100];
    size_t size_;
};
@Reviewer
生配列利用をvectorに統一してください。容量固定要件がある場合もvector::reserve活用で代替可能です。

問題点

  • 固定長制約をコード埋め込み
  • ライフサイクル責任が曖昧
  • API設計でsize情報の別管理発生

改善例

修正例:vectorによる責務統一
class RequestBatch {
public:
    explicit RequestBatch(size_t capacity) {
        logs_.reserve(capacity);
    }

    void add(const ApiRequestLog& log) {
        if (logs_.size() < logs_.capacity()) {
            logs_.push_back(log);
        }
    }

    const std::vector<ApiRequestLog>& getLogs() const {
        return logs_;
    }

private:
    std::vector<ApiRequestLog> logs_;
};
  • 固定長性はreserve + size判定で自然表現
  • 外部APIにvector参照を直接渡せる
  • 所有権・管理責任がvectorに吸収

良くない実装例: ケース2

次はvectorを生ポインタとして外部公開してしまったケースです。

ポインタ公開誤用例
class RequestBatch {
public:
    explicit RequestBatch(size_t capacity) {
        logs_.reserve(capacity);
    }

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

    const ApiRequestLog* data() const {
        return logs_.data();
    }

    size_t size() const {
        return logs_.size();
    }

private:
    std::vector<ApiRequestLog> logs_;
};
@Reviewer
生ポインタでの配列公開は避けてください。const vector参照を公開すれば安全性と柔軟性が両立します。

問題点

  • data()公開は境界管理責任を呼び戻す
  • vectorならsize管理も含めて直接公開可能

改善例

修正例:vector参照公開
const std::vector<ApiRequestLog>& getLogs() const {
    return logs_;
}
  • 呼び出し側の境界管理責任が消滅
  • API契約の責務が自然に整理される

PlantUMLで移行責務整理

UML Diagram

観点チェックリスト

まとめ

配列→vector移行は単なる置換作業ではありません。所有権・責務・契約整理の移行でもあります。
レビューアーはコード表面の配列記法だけでなく、

  • 管理責任の移譲
  • API契約の簡素化
  • 安全性の向上

これらの構造改善まで見届けることが重要です。
配列→vector統一レビューは、設計レビュー育成教材として極めて良質なテーマとなります。