9`std::vector`はキャパシティ、拡張、再確保、およびイテレータの無効化(イテレータの安定性)をどのように管理しますか?
`std::vector`は要素を連続したメモリ領域に格納し、サイズ(`size`)とキャパシティ(`capacity`)の両方を追跡します。`size`は構築された要素数であり、`capacity`は新たなメモリ確保が必要になるまでに利用可能な割り当て済み要素ストレージの容量です。要素の追加によってキャパシティを超える場合、`vector`は通常、処理系定義の等比数列的な拡張戦略に従ってより大きなメモリ領域を確保し、既存の要素を移動またはコピーして、古い要素を破棄した上で古いストレージを解放します。`reserve(n)`は`size`を変更せずに`capacity`を増やしますが、`resize(n)`は要素を構築または破棄することで`size`を変更します。再確保が発生すると、既存の要素を指すイテレータ、参照、ポインタはすべて無効化されます。また、再確保が発生しなくても、`insert`や`erase`などの操作は変更位置またはそれ以降の位置にあるイテレータ等を無効化する可能性があります。
std::vector<int> v;
v.reserve(100); // capacity >= 100, size == 0
v.push_back(1); // size == 1
v.resize(10); // size == 10, adds nine zero-initialized ints
std::cout << v.size() << " " << v.capacity() << "\n";
AI コーチを使ってこの質問に答えてみる
10連続メモリ配置型およびノードベースの標準コンテナについて、どのような無効化規則(イテレータや参照の無効化)を把握しておくべきですか?
無効化規則は、コンテナの種類および実行する操作によって異なります。
`vector` や `string` などの連続メモリ配置型コンテナは、イテレータや参照の安定性が脆弱です。要素の追加に伴うメモリの再確保(reallocation)が発生すると、すべてのイテレータ、参照、ポインタが無効化されます。また、再確保が発生しない場合でも、`insert` や `erase` は要素をシフトさせるため、変更箇所およびそれ以降の位置にあるイテレータや参照が無効化されます。
`list`、`map`、`set`、およびそれらの `multi` 系のようなノードベースの順序付きコンテナでは、一般に要素の挿入(`insert`)を行っても削除されない既存要素へのイテレータや参照は維持されます。要素を削除(`erase`)した場合は、その削除された要素へのイテレータと参照のみが無効化されます。
順序なしコンテナ(unordered containers)もノードに要素を格納するため、リハッシュ(rehash)が発生しても要素への参照やポインタは一般に維持されますが、イテレータは無効化されます。
`deque` は分割ストレージ構造を持つため、独自の規則が適用されます。実務では、コンテナの変更をまたいでイテレータや参照を保持する前に、対象のコンテナと操作に応じた具体的な規則を確認することが不可欠です。
std::map<int, std::string> m = {{1,"a"}, {2,"b"}, {3,"c"}};
for (auto it = m.begin(); it != m.end(); ) {
if (it->first % 2 == 1)
it = m.erase(it); // returns next iterator
else
++it;
}
AI コーチを使ってこの質問に答えてみる
11バックエンドのルックアップテーブルにおいて、std::map、std::unordered_map、およびフラットマップ形式(flat-map-style)のコンテナを比較してください。
std::map は順序付きの(通常は木構造ベースの)連想コンテナであり、検索・挿入・削除を対数時間で行います。ソート順でのイテレーション、範囲クエリ、または順序保証が必要な場合に有用です。std::unordered_map はハッシュテーブルに基づいており、キーの順序は持ちませんが、完全一致のキー操作を平均して定数時間で行います。ハッシュ計算が適切であれば、大規模で変更の多いルックアップテーブルの適切なデフォルト選択肢となります。フラットマップ形式のコンテナはソート済みのキー/値のペアを連続したメモリ領域に格納するため、優れたキャッシュ局所性と、高速なイテレーションおよび二分探索による検索を提供しますが、中間への挿入や削除には線形時間がかかります。バックエンドのルックアップテーブルでは、ワークロードに順序や範囲検索が必要か、主に完全一致検索か、頻繁な変更が発生するか、予測可能なレイテンシが必要か、メモリオーバーヘッドやキャッシュの振る舞いなどを考慮して選択します。
// Exact lookup, frequently updated:
std::unordered_map<std::string, User> users_by_id;
// Need sorted iteration or lower_bound/range queries:
std::map<std::string, User> users_by_id_ordered;
// Build once, query many times: vector sorted by key is a common flat-map style.
std::vector<std::pair<std::string, User>> users;
std::sort(users.begin(), users.end(), [](auto const& a, auto const& b) {
return a.first < b.first;
});
auto it = std::lower_bound(users.begin(), users.end(), std::string_view{"u123"},
[](auto const& p, std::string_view key) { return p.first < key; });
AI コーチを使ってこの質問に答えてみる
12std::optionalと、値が存在しない状態を表すためのバックエンドにおける代表的なユースケースについて説明してください。
std::optional<T>は、保持されているT型の値、または「値が存在しない」状態のいずれかを表します。空の状態はstd::nulloptによって表され、コードからはhas_value()を呼び出すかboolコンテキストで評価して確認でき、*演算子やvalue()で値にアクセスし、value_or()でデフォルト値を指定できます。バックエンドコードでは、NULL許容のデータベースフィールド、リクエストや設定のオプションフィールド、データが存在しないことが想定されるキャッシュやリポジトリのミス、-1や空文字列のような番兵値では曖昧になるドメイン状態の表現などに有用です。これは値の欠落をモデル化するものであり、ポリモーフィズムや詳細なエラー情報を表現するためのものではありません。
struct UserProfile {
std::string id;
std::optional<std::string> display_name; // absent if user has not set it
};
std::string label(UserProfile const& u) {
return u.display_name.value_or("anonymous");
}
AI コーチを使ってこの質問に答えてみる
13std::string_view と std::span とは何ですか。また、所有権を持たないビュー(non-owning view)はどのようなオブジェクトの寿命に関する危険性をもたらしますか。
std::string_view は連続した文字シーケンスの所有権を持たないビューであり、std::span<T> は型 T の連続したシーケンスの所有権を持たないビューです。これらはメモリの割り当てや所有を行わずにポインタと長さのみを保持するため、ゼロコピーの引数やバッファ操作インターフェイスに有用です。主な危険性はオブジェクトの寿命(lifetime)にあります。参照先のストレージはビューよりも長く生存しなければならず、ビューが使用されている間に無効化されてはなりません。一時オブジェクト、ローカル変数、破棄されたオブジェクト、またはメモリ再割り当てが行われたコンテナへのビューを戻り値として返したり保持したりすると、ダングリングビュー(dangling view)が発生する原因となります。
std::string_view bad() {
std::string s = "hello";
return std::string_view{s}; // dangling after return
}
void ok(std::string_view name) {
// safe only during this call if caller's data outlives the call
}
AI コーチを使ってこの質問に答えてみる