C++レビュー|reserve・capacity活用設計と動的確保責任のレビュー整理
この記事のポイント
- reserve・capacityの使い方をレビューアー視点で体系整理
- 動的確保コストが設計責任にどう現れるかを理解
- API契約やスケーラビリティ設計まで含めた責任整理を学ぶ
そもそもreserve/capacityとは
C++標準ライブラリのvectorやstringなどには内部容量(capacity)という概念がある。
これは「現在確保済みの領域量」を表し、要素数(size)とは異なる。
| メソッド | 意味 |
|---|---|
reserve(n) |
少なくともn要素分の領域を事前確保 |
capacity() |
現在確保済みの最大要素数 |
size() |
実際に格納されている要素数 |
shrink_to_fit() |
余剰領域を削減(ヒント程度) |
- reserveは確保量保証API
- capacityは確保済み量確認API
なぜこれをレビューするのか
レビューアー視点
reserve・capacityの活用設計は以下の責任論点を内包する。
-
動的再確保コストの抑制設計責任
→ push_back連打による指数的確保膨張を制御しているか -
事前見積責任
→ 処理開始前に件数推計が設計に反映されているか -
API契約整理
→ 呼び出し側が件数見積可能ならreserve責務を委譲すべきか -
例外安全性設計
→ 確保失敗時の障害モード整理はできているか -
スケーラビリティ保証
→ 大量件数想定時も設計が安定するか
開発者視点
- push_backし続ければよいと考えてしまう
- capacity()の意味を理解していない
- shrink_to_fit()を乱用
- APIで件数を予測させる設計に不慣れ
- reserveしないまま巨大処理実行
レビューアーはこれら「拡張コスト軽視設計」を読み取り、改善提案を行う。
良い実装例
ユースケース:APIリクエストログ蓄積バッチ処理(件数事前想定あり)
#include <vector>
#include <string>
#include <cstdint>
struct ApiRequestLog {
std::string requestId;
int responseCode;
int64_t requestedAt;
};
class RequestLogCollector {
public:
explicit RequestLogCollector(size_t expectedSize) {
logs_.reserve(expectedSize);
}
void add(const ApiRequestLog& log) {
logs_.push_back(log);
}
const std::vector<ApiRequestLog>& getLogs() const {
return logs_;
}
private:
std::vector<ApiRequestLog> logs_;
};- 事前容量予測→reserve活用
- push_backコストが安定化
- 再確保発生率が激減
レビュー観点
- 初期容量見積が設計段階で行われているか
- reserve責任がAPI設計上どちらに所属するか整理されているか
- push_back呼出件数が大量化しない設計か
- capacity()確認コードが正しく設計意図と結びついているか
良くない実装例: ケース1
以下はreserve未使用で毎回動的確保を強いる例。
class RequestLogCollector {
public:
void add(const ApiRequestLog& log) {
logs_.push_back(log);
}
private:
std::vector<ApiRequestLog> logs_;
};
@Reviewer事前に件数予測可能であればreserveを活用し、再確保頻度を減らしてください。
問題点
- push_backが内部で頻繁に再確保
- ムーブ・コピーコスト累積
- スケーラビリティ低下
改善例
explicit RequestLogCollector(size_t expectedSize) {
logs_.reserve(expectedSize);
}良くない実装例: ケース2
次はresizeと混同し、要素構築済み確保をしてしまった例。
logs_.resize(expectedSize);
@Reviewerresizeは領域確保+要素構築を行います。単純確保ならreserveを使用してください。
問題点
- 不要な要素構築発生
- デフォルトコンストラクタ依存発生
- 意図と異なるsize()になる
改善例
logs_.reserve(expectedSize);- reserve:領域のみ確保
- resize:領域確保+要素構築
良くない実装例: ケース3
次はshrink_to_fit()を安易に使用し性能低下を誘発する例。
logs_.shrink_to_fit();
@Reviewershrink_to_fit()は実行コストが高いケースがあります。必要性を設計責任で明文化してください。
問題点
- 実行タイミング不定
- メモリ断片化誘発
- 多用厳禁API
改善例
// shrink_to_fitは原則設計段階で禁止し、必要箇所でのみ限定適用API契約とreserve責任分離パターン
パターンA:呼出側が見積可能
RequestLogCollector collector(expectedSize);- reserve責任は呼出側
- APIはconstructor引数受取設計
パターンB:内部で見積責任を持つ
collector.prepare();- データ特性に応じた内部見積計算
- APIが自律的にreserve実施
コンテナ毎のreserve/capacity整理
| コンテナ | reserve効果 | capacity維持性 |
|---|---|---|
| vector | 高い | 正確保証 |
| string | 高い | 正確保証 |
| deque | 無意味 | 概念無し |
| list | 無意味 | 概念無し |
| map/set | 無意味 | 概念無し |
| unordered_map | 効果大 | バケット数事前確保 |
- unordered_mapはreserveでバケット数指定が可能(要注意API差異)
PlantUMLで設計責務整理
観点チェックリスト
実務レビューFAQ
Q1. reserveしなくてもC++は勝手に伸びるのでは?
→ その通り。ただし指数成長する都度のムーブ/コピーコストが累積する。
Q2. resizeとreserveの違い?
→ resizeは要素構築+領域確保。reserveは領域確保のみ。
Q3. shrink_to_fitはどこで使う?
→ 繰返し巨大縮小が発生する特定箇所のみ。
Q4. unordered_map::reserveは要注意?
→ バケット数指定。件数見積時に掛け率注意(負荷係数考慮)。
Q5. API契約でreserve責任を委譲すべき?
→ 呼出側が件数推計可能な時は積極委譲。ただし利用難易度も考慮。
まとめ
reserve/capacityの活用は性能最適化と設計責任分離の重要設計項目です。
レビューアーは、
- 動的確保回数制御責任
- 初期容量見積責任
- API契約での責務分離整理
- コンテナ種類別適用可否整理
これらを読み解き、安全にスケーラブルなコード設計へ導く役割を担います。
レビューアーがreserveを設計レビューで語れれば実務設計が強くなる。
