C++レビュー|配列からstd::vector移行判断と安全な設計リファクタリングレビュー
この記事のポイント
- 生配列利用の設計負債をレビューアー視点で読み解ける
- 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;
}
}
@ReviewerAPI契約もvector受取にリファクタリングしてください。ポインタ+サイズ契約はバラけやすく、vector受取なら統一責務になります。
改善例
改善例③
void save(const std::vector<ApiRequestLog>& logs) {
for (const auto& entry : logs) {
std::cout << entry.requestId << std::endl;
}
}
観点チェックリスト
まとめ
生配列 → vector移行レビューは
「責務分散設計 → 責務吸収設計」 への進化レビューです。
レビューアーは
- どこに生配列が残っているか?
- 呼び出し側のサイズ責務が残っていないか?
- 例外安全性は保証されているか?
を読み取り、設計の安全領域へ移行誘導する視点が重要になります。