C++レビュー|配列からvectorへの移行方針と設計レビュー観点の統一整理
この記事のポイント
- 配列から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で移行責務整理
観点チェックリスト
まとめ
配列→vector移行は単なる置換作業ではありません。所有権・責務・契約整理の移行でもあります。
レビューアーはコード表面の配列記法だけでなく、
- 管理責任の移譲
- API契約の簡素化
- 安全性の向上
これらの構造改善まで見届けることが重要です。
配列→vector統一レビューは、設計レビュー育成教材として極めて良質なテーマとなります。
