この記事のポイント

  • reserve・capacityの使い方をレビューアー視点で体系整理
  • 動的確保コストが設計責任にどう現れるかを理解
  • API契約やスケーラビリティ設計まで含めた責任整理を学ぶ

そもそもreserve/capacityとは

C++標準ライブラリのvectorstringなどには内部容量(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未使用で毎回動的確保を強いる例。

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と混同し、要素構築済み確保をしてしまった例。

resize誤用例
logs_.resize(expectedSize);
@Reviewer
resizeは領域確保+要素構築を行います。単純確保ならreserveを使用してください。

問題点

  • 不要な要素構築発生
  • デフォルトコンストラクタ依存発生
  • 意図と異なるsize()になる

改善例

修正例:reserve専用用途適用
logs_.reserve(expectedSize);
  • reserve:領域のみ確保
  • resize:領域確保+要素構築

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

次はshrink_to_fit()を安易に使用し性能低下を誘発する例。

shrink誤用例
logs_.shrink_to_fit();
@Reviewer
shrink_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で設計責務整理

UML Diagram

観点チェックリスト

実務レビューFAQ

Q1. reserveしなくてもC++は勝手に伸びるのでは?
→ その通り。ただし指数成長する都度のムーブ/コピーコストが累積する。

Q2. resizeとreserveの違い?
→ resizeは要素構築+領域確保。reserveは領域確保のみ。

Q3. shrink_to_fitはどこで使う?
→ 繰返し巨大縮小が発生する特定箇所のみ。

Q4. unordered_map::reserveは要注意?
→ バケット数指定。件数見積時に掛け率注意(負荷係数考慮)。

Q5. API契約でreserve責任を委譲すべき?
→ 呼出側が件数推計可能な時は積極委譲。ただし利用難易度も考慮。

まとめ

reserve/capacityの活用は性能最適化と設計責任分離の重要設計項目です。
レビューアーは、

  • 動的確保回数制御責任
  • 初期容量見積責任
  • API契約での責務分離整理
  • コンテナ種類別適用可否整理

これらを読み解き、安全にスケーラブルなコード設計へ導く役割を担います。
レビューアーがreserveを設計レビューで語れれば実務設計が強くなる。