プロンプトと範囲
C++26 の std::inplace_vector<T, N> がオブジェクト内部の固定キャパシティの連続ストレージで可変長をどのように実現しているかを説明してください。オーバーフロー、例外、ムーブ、イテレータの無効化、および std::vector が依然として適切な選択肢となるケースを網羅した安全な追加(append)ポリシーを設計してください。
std::inplace_vector は、ストレージがオブジェクト内に存在し、キャパシティが非型テンプレートパラメータ N によって固定された状態でサイズが変化する C++26 の連続コンテナです。既知の上限があり、メモリ割り当てを行わないパスに適していますが、std::array ではないため、無制限に拡張することはできません。回答は構文ではなく、キャパシティの契約とオブジェクトの生存期間(lifetime)を中心に据える必要があります。
面接官が評価するポイント
- サイズ、キャパシティ、オブジェクト内ストレージ、動的メモリ割り当ての区別。
- キャパシティ
Nでのオーバーフローセマンティクスとエラーポリシーの説明。 - 連続ストレージ、ムーブ、参照、イテレータの無効化の理解。
- 要素構築時の例外についての議論と、強い保証(strong guarantee)が可能かどうかの判断。
Nがオブジェクトサイズ、スタックレイアウト、ABI に与える影響の認識。inplace_vector、array、vector、およびその他のコンテナ間での適切な選択。
確認すべき明確化のための質問
Nはコンパイル時に証明可能な上限値ですか、それとも実行時構成ですか?- オーバーフローはプログラマのエラー、回復可能な入力、またはドロップすべきメッセージのどれですか?
- 要素はムーブ可能、コピー可能、または例外を送出する可能性がありますか?
- コンテナはスタック、プール、共有メモリ、またはホットパスオブジェクトのどこに配置されますか?
- 呼び出し元は要素への参照、ポインタ、またはイテレータを保持しますか?
30秒の簡潔な回答
私は N を明示的なキャパシティ契約とし、暗黙の拡張ではなく、拒否、エラーの返却、またはバックプレッシャーの適用といったオーバーフローポリシーを選択します。inplace_vector は連続アクセスを維持しますが、バッファをオブジェクト内に格納するため、オブジェクトサイズとムーブコストは N とともに増大します。追加前にサイズをチェックし、要素の例外保証に合致した構築パスを使用します。ヒープの再割り当てがなくても、挿入によって参照やイテレータが無効化される可能性があります。上限が不明な場合や償却成長が必要な場合は、std::vector を使用します。
ステップバイステップの詳細解説
1. キャパシティと生存期間の定義
std::inplace_vector<T, N> のサイズは 0 から N の間であり、要素は連続したアドレスを占有します。N は型の一部です。構築時に N 個すべての要素がデフォルト構築されるわけではありません。要素は挿入時に構築され、生存している要素のみが破棄されます。
2. 追加契約の定義
追加を行う前に size() == capacity() を確認し、オーバーフローをビジネスエラー、ドロップポリシー、または上流へのバックプレッシャーにマッピングします。reserve を拡張として扱ったり、末尾を暗黙的に上書きしたりしてはなりません。バッチ追加の場合は、最初に行き先コンテナの残りのキャパシティを確認するか、部分的な成功を定義して挿入された要素数を返します。
template<class T, std::size_t N>
bool try_append(std::inplace_vector<T, N>& out, T value) {
if (out.size() == out.capacity()) return false;
out.push_back(std::move(value));
return true;
}この例はキャパシティポリシーのみを表現しています。実際の戻り値と状態保証は、T のムーブコンストラクタが例外を送出する可能性があるかどうかに依存します。
3. 例外安全性の処理
要素の構築やムーブが例外を送出する可能性がある場合、コンテナの不変条件を維持し、操作が基本保証(basic guarantee)と強い保証(strong guarantee)のどちらを提供するのかを明示します。バッチ処理では一時コンテナに構築してからムーブすることもできますが、そのムーブ自体も例外を送出する可能性があります。型名自体がトランザクション的なコミットを約束するわけではありません。
4. 参照とイテレータの議論
要素を挿入すると後続の要素が移動する可能性があるため、保持されている参照、ポインタ、およびイテレータは標準の無効化ルールに従う必要があります。ヒープの再割り当てがないからといって、位置が決して変わらないという意味ではありません。呼び出し元が要素のアドレスを保持するのを推奨する代わりに、API はインデックスや安定したハンドルを返すことができます。
5. オブジェクトサイズとムーブコストの比較
オブジェクト内バッファを持つため、sizeof(inplace_vector<T, N>) は一般的に N および要素のアライメントに応じて大きくなります。大きなキャパシティをスタックフレーム、メッセージオブジェクト、または頻繁にコピーされる構造体に配置すると、スタック、キャッシュ、およびムーブのコストが増加する可能性があります。メモリ割り当てを回避することが常に高速であると主張する前に、メモリ配置(layout)を測定してください。
6. 代替手段の選択
上限が既知であり、連続アクセスが必要で、メモリ割り当てを行わないパスが望まれる場合は inplace_vector を選択します。要素数が固定の場合は std::array を、上限が不明な場合や動的拡張が必要な場合は std::vector を使用し、安定したノードアドレスが重要な場合は別のコンテナを使用します。キャパシティ、生存期間、局所性、およびエラーセマンティクスによって決定します。
7. テストと観察
空の状態、ちょうど満杯、キャパシティ超過、例外を送出する要素、ムーブとコピー、ネストされたオブジェクト、および大きな N についてテストします。オーバーフロー数、バッチの部分的成功、オブジェクトサイズ、ホットパスのレイテンシを記録し、生存期間のエラーにはサニタイザを使用します。コンパイラとライブラリが C++26 をサポートしている場合は、機能テストマクロと実装の違いを確認してください。
高品質な回答例
私は N をコンパイル時のキャパシティ契約として扱います。サイズは変化しますが N を超えることはできず、要素はオブジェクト内部で連続しています。追加前に残りのキャパシティを確認し、上書きするのではなく、オーバーフローをエラーまたはバックプレッシャーにマッピングします。例外を送出する可能性のある要素に対して基本保証または強い保証を定義し、バッチが部分的に成功し得るかを明示します。ヒープの伸長がないからといって、参照やイテレータが自動的に安定するわけではありません。
また、オブジェクトサイズ、スタックとキャッシュへの負荷、ムーブコスト、ABI も測定します。上限が既知であり、ホットパスで連続アクセスする場合は inplace_vector が適しています。上限が不明な場合や償却成長を伴う場合は std::vector を使用し、要素数が固定の場合は std::array を使用します。キャパシティと例外の境界をテストし、オーバーフローとレイテンシを観察します。
よくある間違い
- 伸長する vector として扱うこと → N で停止するため、オーバーフローセマンティクスを明記する必要があります。
- オブジェクト内ストレージにより参照が安定したままであると思い込むこと → 要素の移動により位置が変わるため、無効化ルールに従う必要があります。
- すべての要素が構築されていると思い込むこと → 現在のサイズ分のみが有効であるため、ストレージと生存期間を切り離して考えます。
- オブジェクトサイズを無視してゼロアロケーションのみに着目すること → N が大きいとスタックとキャッシュを圧迫するため、レイアウトとムーブを測定します。
- バッチの例外が自動的にロールバックされると思い込むこと → 要素のムーブが例外を送出する可能性があるため、保証を明記してテストします。
- 不明な上限に対して無理に適用すること → ビジネス入力が拒絶されたり切り詰められたりするため、vector や他のコンテナを選択します。
フォローアップの質問と回答
inplace_vector<T, N> は std::array<T, N> とどう異なりますか?
サイズが 0 から N まで変化し、要素は必要に応じて構築されます。配列は常に N 個の要素を含みます。どちらもオブジェクト内ストレージを使用しますが、生存期間と API 契約が異なります。
キャパシティ満杯時に例外を送出すべきですか?
それはビジネス要件に応じた選択です。回復可能な入力は通常エラーを返すかバックプレッシャーを適用し、プログラマのエラーにはアサーションや例外を使用できます。暗黙の上書きは許容されず、バッチの部分的成功セマンティクスは明示的である必要があります。
inplace_vector がムーブされると何が起こりますか?
要素は移動先オブジェクトのバッファにムーブまたはコピーされ、そのコストはサイズと要素型に関連します。単にヒープポインタを交換するだけではありません。
なぜ非常に大きな N を選ばないのですか?
バッファによってオブジェクト、スタック、コピー、キャッシュのコストが増加するためです。実際の分布、テールのキャパシティ、オーバーフローのコストに基づいて N を選択してください。
標準ライブラリが C++26 をサポートしていない場合はどうしますか?
機能テストマクロと実装ドキュメントを確認し、明確なビルド要件を設定するか代替手段を選択します。実験的な実装を標準的な動作として暗黙的に扱ってはなりません。
要素の参照を安全に保つにはどうすればよいですか?
参照の生存期間を制限し、インデックスや安定したハンドルを優先し、挿入、ムーブ、または破棄後に古い参照を使用することを禁止します。サニタイザと境界テストで検証します。