生メモリへのビューとして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つになってしまうので、データセットが非常に大きくなる可能性があるので避けたいところです。

質問へのコメント (11)

C++20の std::span です。

C++20が使えるのであれば、std::spanを使うことができます。これはポインタと長さのペアで、連続した要素のシーケンスを表示します。これは std::string_view の一種で、std::spanstd::string_view はどちらも所有権を持たないビューですが、std::string_view は読み取り専用のビューです。

ドキュメントより。

クラステンプレート span は オブジェクトの最初の要素を持つ連続したシーケンス の位置がゼロになります。スパンは、静的エクステントを持つことができます。 シーケンスの要素数が既知であり、エンコードされている場合 型、または動的エクステント。

ですから、次のようにすればうまくいくでしょう。


#include <span>
#include を使用しています。
#include を使用しています。

int main() {
    int data[] = { 5, 3, 2, 1, 4 }.
    std::span s{data, 5}.

    std::sort(s.begin(), s.end())。

    for (auto const i : s) {
        std::cout 
解説 (2)
ソリューション

問題は、std::vector にはオブジェクトの所有権があるので、std::vector は初期化した配列の要素のコピーを作成しなければならないことです。

これを避けるには、配列に slice オブジェクトを使うことができます (つまり、std::string_viewstd::string に似ているということです)。配列の最初の要素への生のポインタと配列の長さを取得してインスタンスを作成する array_view クラスのテンプレート実装を独自に書くことができます。

#include 

template
class array_view {
   T* ptr_;
   std::size_t len_;
public:
   array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}

   T& operator[](int i) noexcept { return ptr_[i]; }
   T const& operator[](int i) const noexcept { return ptr_[i]; }
   auto size() const noexcept { return len_; }

   auto begin() noexcept { return ptr_; }
   auto end() noexcept { return ptr_ + len_; }
};

配列の先頭へのポインタと配列の長さを保持しているだけです。そのため、array_view オブジェクトは構築もコピーも安価です。

array_viewbegin()end()メンバ関数を提供しているので、標準的なライブラリのアルゴリズム(std::sort,std::find,std::lower_bound` など)を利用することができます。


#define LEN 5

auto main() -> int {
   int arr[LEN] = {4, 5, 1, 2, 3};

   array_view av(arr, LEN);

   std::sort(av.begin(), av.end());

   for (auto const& val: av)
      std::cout 
解説 (2)

algorithm-libraryはイテレータで動作するので、配列を維持することができます。

ポインターと配列の長さがわかる場合

ここでは、生のポインターをイテレータとして使用できます。イテレータがサポートするすべての操作(インクリメント、等質性の比較、値の比較など)をサポートしています。


#include 
#include 

int *get_data_from_library(int &size) {。
    static int data[] = {5,3,2,1,4};

    size = 5;

    データを返します。
}

int メイン()
{
    int size;
    int *data = get_data_from_library(size);

    std::sort(data, data + size)となります。

    for (int i = 0; i < size; i++)
    {
        std::cout 
解説 (1)

生の配列でイテレータを取得し、アルゴリズムで使用することができます。


    int data[] = {5,3,2,1,4};
    std::sort(std::begin(data), std::end(data));
    for (auto i : data) {
        std::cout 
解説 (4)

この値にアクセスして変更するために std::vector を使用したいと思います。

できません。それは std::vector のためのものではありません。std::vector` は自身のバッファを管理しますが、それは常にアロケータから取得されます。他のバッファの所有権を取ることは決してありません (同じ型の別のベクタからのものを除いて)。

一方で、その必要はありません。

理由は、そのデータにからのアルゴリズム(ソート、要素の入れ替えなど)を適用する必要があるからです。

それらのアルゴリズムはイテレータ上で動作します。ポインタは配列へのイテレータです。ベクトルは必要ありません。

std::sort(data, data + size);

の関数テンプレートとは異なり、range-for,std::begin/std::endやC++20の範囲のようなツールはイテレータのペアだけでは動作しませんが、ベクトルのようなコンテナでは動作します。範囲として動作し、これらのツールで動作するイテレータ + サイズのラッパークラスを作成することができます。C++20 では、このようなラッパーが標準ライブラリに導入される予定です。std::span) に導入されます。

解説 (0)

コピーを取らずに std::vector でこれを行うことはできません。 std::vector は、フードの下に持っているポインタを所有し、提供されているアロケータを使ってスペースを割り当てます。

C++20 をサポートしているコンパイラがあれば,まさにこの目的のために作られた std::span を使うことができます. これは、ポインタとサイズを、C++のコンテナ・インターフェースを持つ"コンテナ"にラップします。

C++20をサポートしていない場合は、標準バージョンのベースとなったgsl::spanを使うことができます。

もし他のライブラリをインポートしたくないのであれば、必要な機能に応じて自分で実装することも簡単にできます。

解説 (0)

実際には、カスタムアロケータの機能を悪用して、表示したいメモリへのポインタを返すことで、ほとんど std::vector を使うことができます。これは標準では動作が保証されていませんが(パディング、アライメント、戻り値の初期化、初期サイズを代入する際に手間がかかりますし、非プリミティブの場合はコンストラクタをハックアップする必要があります)、実際には十分な調整がなされていると思います。

絶対にやってはいけません。醜いし、びっくりするし、ハックしているし、不必要だからです。標準ライブラリのアルゴリズムは、ベクターと同様に生の配列でも動作するようにすでに設計されています。その詳細については、他の回答を参照してください。

解説 (3)

また、std::span が [tag:c++20] や gsl:span に入ってくることについての他の良い提案もありますが、それまでの間、独自の(軽量な) span クラスを含めることは十分に簡単です(自由にコピーしてください)。


template
struct span {
    T* first;
    size_t length;
    span(T* first_, size_t length_) : first(first_), length(length_) {};
    using value_type = std::remove_cv_t;//primarily needed if used with templates
    bool empty() const { return length == 0; }
    auto begin() const { return first; }
    auto end() const { return first + length; }
};

static_assert(_MSVC_LANG 
解説 (6)

C++11 以降で利用可能な [std::reference_wrapper][1] を利用するとよいでしょう。


#include 
#include 
#include 
#include 

int main()
{
    int src_table[] = {5, 4, 3, 2, 1, 0};

    std::vector< std::reference_wrapper< int > > dest_vector;

    std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
    // if you don't have the array defined just a pointer and size then:
    // std::copy(src_table_ptr, src_table_ptr + size, std::back_inserter(dest_vector));

    std::sort(std::begin(dest_vector), std::end(dest_vector));

    std::for_each(std::begin(src_table), std::end(src_table), [](int x) { std::cout 
解説 (10)

他の人が指摘しているように、std::vector は基礎となるメモリを所有しなければならないので(カスタムアロケータをいじらなければ)、使用できません。

他の人も c++20 の span を推奨していますが、明らかに c++20 が必要です。

私は [span-lite][1] スパンをお勧めします。サブタイトルを引用すると

span lite - C++98, C++11 以降の C++20 ライクなスパンをシングルファイルのヘッダのみのライブラリで提供します。

これは、所有権のないミューティング可能なビューを提供します(つまり、要素とその順序をミューティングすることはできますが、挿入はできません)。

あなたの例


#include 
#include 
#include 

#include 

static int data[] = {5, 1, 2, 4, 3};

// For example
int* get_data_from_library()
{
  return data;
}

int main ()
{
  const std::size_t size = 5;

  nonstd::span v{get_data_from_library(), size};

  std::sort(v.begin(), v.end());

  for (auto i = 0UL; i < v.size(); ++i)
  {
    std::cout 
解説 (0)