C++レビュー|イテレータ範囲外アクセス防止設計と責任整理レビュー技術
この記事のポイント
- イテレータ範囲安全の設計責任をレビューアー視点で整理
- 境界条件整理、契約整理、安全設計のレビュー観点を体系化
- 実務的に見逃されやすい「微細なバグの芽」を発見する訓練記事
そもそもイテレータ範囲外アクセスとは
C++のイテレータは基本的に範囲を超えたアクセスは未定義動作になります。
auto it = vec.end();
++it; // UB
*it; // UB範囲安全とは何か
- begin() <= it < end()
- これを常に保証するのが「範囲安全」
- イテレータ境界超過はバグ源として非常に見逃されやすい
- STLは範囲契約型APIで構成されている
なぜこれをレビューするのか
レビューアー視点
イテレータ範囲安全は以下の設計責任整理が必要です。
-
境界管理責任
→ begin/endの一貫性保証 -
範囲契約遵守責任
→ STL APIで提供する範囲契約を逸脱していないか確認 -
イテレータ寿命保証責任
→ コンテナ変更による失効設計整理 -
副作用混在抑止責任
→ erase中のイテレータ操作責任 -
スケーラビリティ保証責任
→ 件数増加時の境界管理破綻回避
開発者視点
- size()を直接for条件に使う
- end()越えを静的保証できないコードを書く
- erase後のイテレータ無効化を軽視
- insert中の並行参照で境界崩壊
- API契約に境界条件を盛り込まない
レビューアーはこれら「境界責任軽視文化」をレビュー段階で是正します。
良い実装例(範囲for利用)
ユースケース:APIレスポンス全件処理
良い実装例:範囲forで境界責任転嫁
#include <vector>
#include <string>
struct ApiRequestLog {
std::string requestId;
};
void process(const std::vector<ApiRequestLog>& logs) {
for (const auto& log : logs) {
handle(log);
}
}- 範囲for文は範囲安全設計文化の代表
- イテレータ境界責任が完全内部化
良い実装例(アルゴリズム使用)
良い実装例:count_ifで範囲契約内包
int countFailures(const std::vector<ApiRequestLog>& logs) {
return std::count_if(logs.begin(), logs.end(),
[](const ApiRequestLog& log) {
return log.responseCode >= 500;
});
}- アルゴリズム利用は範囲契約化最大の武器
レビュー観点
- begin/endセットの整合性維持されているか
- 明示indexアクセスでsize()-1越境が起きていないか
- erase後のイテレータ失効責任整理ができているか
- API契約内に範囲条件を整理できているか
良くない実装例: ケース1
以下はindex比較で範囲超過が容易に生まれる例。
index誤用例
for (size_t i = 0; i <= logs.size(); ++i) { // <= になってる
handle(logs[i]);
}
@Reviewersize()は「要素数」。indexはsize()未満まで限定しないと越境します。
問題点
- i == size()時に未定義動作
- 境界条件設計崩壊
改善例
修正例:< に修正
for (size_t i = 0; i < logs.size(); ++i) {
handle(logs[i]);
}良くない実装例: ケース2
次はerase後イテレータ再利用で無効化される例。
erase失効例
for (auto it = logs.begin(); it != logs.end(); ++it) {
if (pred(*it)) {
logs.erase(it);
}
}
@Reviewererase後はイテレータ失効します。戻り値it再代入が必要です。
問題点
- eraseが次要素失効を誘発
- 破壊的副作用混在
改善例
修正例:erase戻り値再利用
for (auto it = logs.begin(); it != logs.end(); ) {
if (pred(*it)) {
it = logs.erase(it);
} else {
++it;
}
}良くない実装例: ケース3
次はerase-removeイディオム適用忘れによる境界崩壊例。
erase忘却例
std::remove_if(logs.begin(), logs.end(), pred);
@Reviewerremove_if後はerase統合しないと未削除範囲が残ります。
問題点
- erase統合責任が放棄される
- size()との整合性崩壊
改善例
修正例:remove-eraseイディオム適用
logs.erase(std::remove_if(logs.begin(), logs.end(), pred), logs.end());API契約整理パターン
パターンA:イテレータ引数API
void processLogs(std::vector<ApiRequestLog>::iterator first,
std::vector<ApiRequestLog>::iterator last);- 呼出側が範囲管理責任保持
- 汎用API向き
パターンB:コレクション引数API
void processLogs(const std::vector<ApiRequestLog>& logs);- API側が境界保証責任保持
- 安全性高
範囲安全戦略早見表
| 処理形態 | 推奨戦略 |
|---|---|
| 単純全件走査 | 範囲for |
| 条件件数計測 | count_if |
| 条件抽出 | copy_if |
| 条件削除 | remove_if+erase |
- アルゴリズム文化は範囲安全文化
PlantUMLで設計責任整理
観点チェックリスト
実務レビューFAQ
Q1. indexループは必ず悪?
→ 明示目的なら容認。ただし境界誤差をレビューで常に監視。
Q2. erase後のイテレータ失効は規格上保証?
→ erase戻り値使用が標準設計文化。失効確認はレビュー必須。
Q3. remove_ifは削除処理?
→ 前詰めのみ。erase統合必須。
Q4. アルゴリズム使用は過剰抽象?
→ 逆。範囲安全の設計文化基盤。
Q5. API契約で範囲管理委譲はいつ妥当?
→ 汎用API時はiterator引数。業務API時はコレクション受領が安全。
まとめ
イテレータ範囲安全レビューは実務品質レビューの基本技術である。
レビューアーは
- 境界責任整理
- erase副作用整理
- remove-erase統合責任整理
- API契約昇格整理
を読み解き、「範囲管理は設計者の責任」を文化化するレビュー技術を育成する必要がある。
レビューアーが境界責任整理を体系化できると設計品質は非常に安定する。
