最終更新:2026-10-12
C++20 std::span の設計と落とし穴¶
0.きっかけ¶
従来の C/C++ コードでは、配列にアクセスするためにポインタ+サイズを使うことが多いでしょう。例えば:
span はこの操作を簡略化し、次のようにできます:
では、span とは一体何なのか、その使用シーンと落とし穴にはどんなものがあるのか?本節で詳しく掘り下げます。
1. span とは?¶
C++20 において、span は軽量な範囲タイプのコンテナで、**連続したメモリ領域**へのアクセスを提供します。span はビュー(view)としてデータを所有せず、データへの単純な参照であり、従来の C スタイル配列やポインタの代替としてよく使われます。任意の型の配列や std::vector のような標準コンテナを格納でき、これらのデータをより安全でモダンな方法で扱う手段を提供します。
重要なポイントは次の通りです:
- 連続したメモリ
- データを所有しない
- 参照
span のソースコードは gcc の libstdc++-v3/include/std/span にあります。
span 型には2つの重要なパラメータがあります:
_Type:要素の型。Extent:配列のサイズ。dynamic_extent(デフォルト値)の場合、ビューのサイズが動的に変化できることを示します。
2. span の動的と静的¶
span には2つのモードがあります:静的と動的です。
2.1 静的 span¶
静的 span はコンパイル時にサイズが確定し、その Extent は定数です。静的 span を使う場合、コンパイル時にデータのサイズを知っている必要があります。
例えば:
静的 span のサイズはコンパイル時に固定されるため、境界チェックの省略などのコンパイラ最適化を利用できます。ただし Extent が定数のため、静的 span は実行時にサイズを変更できません。
2.2 動的 span¶
動的 span のサイズは実行時に確定し、Extent はデフォルトで dynamic_extent です。このタイプの span はコンパイル時にサイズを確定する必要がなく、任意のサイズの配列やコンテナに適応できます。
例えば:
動的 span は、コンパイル時にサイズを確定できないケースに適しており、より大きな柔軟性を提供します。
gcc のソースでの実装は比較的単純で、デフォルトは非常に大きな整数(-1 の符号なし数)で、実際の計算は構築時に行われます。
例えば:ここでは ranges::size を使って内部サイズを設定します。詳細は以下のコードをご覧ください。
動的サイズを設定します。
3. subspan¶
span は非常に有用なメンバ関数 subspan を提供します。これにより、既存の span からサブビューを、データをコピーせずに抽出できます。大きなデータセットを扱う際に不要なメモリオーバーヘッドを避けるのに非常に適しています。
subspan() メソッドには2つのオーバーロードがあります:
__offsetと__countを引数に取るもの。__offsetは開始位置、__countはサブ範囲のサイズです(デフォルトはdynamic_extent、つまり元のspanの末尾まで)。- 引数を取らず、テンプレートで実装されたもの。
constexpr auto
subspan() const noexcept
-> span<element_type, _S_subspan_extent<_Offset, _Count>()> {}
template<size_t _Offset, size_t _Count = dynamic_extent>
[[nodiscard]]
constexpr auto
subspan() const noexcept
-> span<element_type, _S_subspan_extent<_Offset, _Count>()> {}
例1:
例2:
4. 使用上の落とし穴¶
span は非常に有用なツールですが、使用時には注意すべきよくある落とし穴もあります:
4.1 解放済みメモリを span に参照させない¶
span 自体はデータを所有せず、データへの参照にすぎません。したがって、span が参照するメモリが解放された場合(例えば元の配列が破棄されたりスコープを抜けたりした場合)、その span にアクセスすると未定義動作になります。
例えば:
std::span<int> create_span() {
int arr[5] = {1, 2, 3, 4, 5};
return std::span<int>(arr, 5);
}
// 错误:返回的 span 引用了栈上临时变量,函数返回后 arr 被销毁
auto s = create_span();
この問題を解決するには、span が参照するデータがその生存期間中有効であることを保証します。データを動的に割り当てたメモリに保持するか、データの生存期間と span の生存期間を一致させることを検討してください。
4.2 底層配列のサイズを変更する際の問題¶
span の底層データへの参照は直接であり、コピーではありません。底層のデータが変化しても、span 自体はその変化を感知しません。そのため、底層の配列やコンテナのサイズを変更すると(例えばメモリの再割り当てにより)、元の span は無効になる可能性があります。
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> s(vec); // s 引用 vec 的数据
// 改变底层容器的大小
vec.push_back(6); // vec 大小变为 6
// 这里 s 仍然引用 vec 的数据,但 vec 可能已经重新分配了内存
// 这可能导致 s 引用失效或未定义行为
関連トピック¶
- C++20 コルーチン入門 | C++那些事
- C++20 channel 実践 | C++那些事
- C++20 Concepts コンセプト | C++那些事
- C++20 constexpr とカンマ式 | C++那些事
- C++20 バイトオーダー endian | C++那些事
- C++20 Lambda 式(前編) | C++那些事
- C++20 モジュール Modules | C++那些事
- C++20 Ranges ライブラリ | C++那些事
- C++20 新機能ガイド | C++那些事
- C++20 [[likely]] と [[unlikely]] | C++那些事