生メモリへのビューとしてstd::vectorを使用する
私は外部のライブラリを使用しており、ある時点で整数の配列への生のポインタとサイズを与えられます。
そこで、std::vector
を使って、生のポインタでアクセスするのではなく、その場でこれらの値にアクセスして変更したいと考えています。
ここでは、その点を説明するための具体的な例を紹介します。
size_t size = 0;
int * data = get_data_from_library(size); // raw data from library {5,3,2,1,4}, size gets filled in
std::vector<int> v = ????; // pseudo vector to be used to access the raw data
std::sort(v.begin(), v.end()); // sort raw data in place
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // display sorted raw data
}
期待される出力。
1
2
3
4
5
理由は、そのデータに対して、<algorithm>
のアルゴリズム(ソート、要素の入れ替えなど)を適用する必要があるからです。
一方で、そのベクターのサイズを変更することはないので、push_back
, erase
, insert
はそのベクターに対して動作する必要はありません。
ライブラリからのデータに基づいてベクターを構築し、そのベクターを修正してデータをライブラリにコピーして戻すこともできますが、それでは完全なコピーが2つになってしまうので、データセットが非常に大きくなる可能性があるので避けたいところです。
36
10
C++20の
std::span
です。C++20が使えるのであれば、
std::span
を使うことができます。これはポインタと長さのペアで、連続した要素のシーケンスを表示します。これはstd::string_view
の一種で、std::span
とstd::string_view
はどちらも所有権を持たないビューですが、std::string_view
は読み取り専用のビューです。ドキュメントより。
ですから、次のようにすればうまくいくでしょう。
問題は、
std::vector
にはオブジェクトの所有権があるので、std::vector
は初期化した配列の要素のコピーを作成しなければならないことです。これを避けるには、配列に slice オブジェクトを使うことができます (つまり、
std::string_view
がstd::string
に似ているということです)。配列の最初の要素への生のポインタと配列の長さを取得してインスタンスを作成するarray_view
クラスのテンプレート実装を独自に書くことができます。配列の先頭へのポインタと配列の長さを保持しているだけです。そのため、
array_view
オブジェクトは構築もコピーも安価です。array_view
は
begin()と
end()メンバ関数を提供しているので、標準的なライブラリのアルゴリズム(
std::sort,
std::find,
std::lower_bound` など)を利用することができます。algorithm-libraryはイテレータで動作するので、配列を維持することができます。
ポインターと配列の長さがわかる場合
ここでは、生のポインターをイテレータとして使用できます。イテレータがサポートするすべての操作(インクリメント、等質性の比較、値の比較など)をサポートしています。
生の配列でイテレータを取得し、アルゴリズムで使用することができます。
できません。それは
std::vector
のためのものではありません。std::vector` は自身のバッファを管理しますが、それは常にアロケータから取得されます。他のバッファの所有権を取ることは決してありません (同じ型の別のベクタからのものを除いて)。一方で、その必要はありません。
それらのアルゴリズムはイテレータ上で動作します。ポインタは配列へのイテレータです。ベクトルは必要ありません。
の関数テンプレートとは異なり、range-for,
std::begin/
std::endやC++20の範囲のようなツールはイテレータのペアだけでは動作しませんが、ベクトルのようなコンテナでは動作します。範囲として動作し、これらのツールで動作するイテレータ + サイズのラッパークラスを作成することができます。C++20 では、このようなラッパーが標準ライブラリに導入される予定です。std::span
) に導入されます。コピーを取らずに
std::vector
でこれを行うことはできません。std::vector
は、フードの下に持っているポインタを所有し、提供されているアロケータを使ってスペースを割り当てます。C++20 をサポートしているコンパイラがあれば,まさにこの目的のために作られた std::span を使うことができます. これは、ポインタとサイズを、C++のコンテナ・インターフェースを持つ"コンテナ"にラップします。
C++20をサポートしていない場合は、標準バージョンのベースとなったgsl::spanを使うことができます。
もし他のライブラリをインポートしたくないのであれば、必要な機能に応じて自分で実装することも簡単にできます。
実際には、カスタムアロケータの機能を悪用して、表示したいメモリへのポインタを返すことで、ほとんど
std::vector
を使うことができます。これは標準では動作が保証されていませんが(パディング、アライメント、戻り値の初期化、初期サイズを代入する際に手間がかかりますし、非プリミティブの場合はコンストラクタをハックアップする必要があります)、実際には十分な調整がなされていると思います。絶対にやってはいけません。醜いし、びっくりするし、ハックしているし、不必要だからです。標準ライブラリのアルゴリズムは、ベクターと同様に生の配列でも動作するようにすでに設計されています。その詳細については、他の回答を参照してください。
また、
std::span
が [tag:c++20] やgsl:span
に入ってくることについての他の良い提案もありますが、それまでの間、独自の(軽量な)span
クラスを含めることは十分に簡単です(自由にコピーしてください)。C++11 以降で利用可能な [
std::reference_wrapper
][1] を利用するとよいでしょう。他の人が指摘しているように、
std::vector
は基礎となるメモリを所有しなければならないので(カスタムアロケータをいじらなければ)、使用できません。他の人も c++20 の span を推奨していますが、明らかに c++20 が必要です。
私は [span-lite][1] スパンをお勧めします。サブタイトルを引用すると
これは、所有権のないミューティング可能なビューを提供します(つまり、要素とその順序をミューティングすることはできますが、挿入はできません)。
あなたの例