使用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_backeraseinsert不需要在该向量上工作。

我可以根据库中的数据构造一个向量,使用修改该向量并将数据复制到库中,但这将是两个完整的副本,我想避免这种情况,因为数据集可能非常大。

对该问题的评论 (12)

C++20的std::span

如果你能够使用C++20,你可以使用std::span,它是一个指针--长度对,给用户提供一个视图到一个连续的元素序列。它是某种 "std::string_view",虽然 "std::span "和 "std::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必须从你初始化它的数组中复制元素,因为它拥有它所包含的对象的所有权。

为了避免这个问题,你可以为数组使用一个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_view对象的构造和复制都很便宜。

由于array_view提供了begin()end()成员函数,所以你可以在上面使用标准的库算法(例如std::sortstd::findstd::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)

由于算法库与迭代器一起工作,你可以保留数组。

对于指针和已知的数组长度

这里你可以使用原始指针作为迭代器。它们支持迭代器所支持的所有操作(增量、平等的比较、值等...)。


#include 
#include 

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

    size = 5。

    返回数据。
}

int main()
{
    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的range虽然不能只对一对迭代器工作,但它们可以对容器如向量工作。可以为iterator+size创建一个包装类,它的行为就像一个范围,并与这些工具一起工作。C++20将在标准库中引入这样的封装类。std::span

评论(0)

你不能用std::vector做这个,而不做一个拷贝。 std::vector拥有它的指针,通过提供的分配器分配空间。

如果你有支持C++20的编译器,你可以使用std::span,它正是为了这个目的而建立的。 它将一个指针和大小包装成一个具有C++容器接口的"容器"。

如果没有,你可以使用gsl::span,这是标准版本的基础。

如果你不想导入另一个库,你可以根据你想拥有的所有功能,自己简单地实现它。

评论(0)

实际上,你几乎可以使用 "std::vector "来做这件事,通过滥用自定义分配器功能来返回一个指向你想查看的内存的指针。标准并不能保证这一点(填充、对齐、返回值的初始化;你必须在分配初始大小时费尽心思,对于非基元,你还需要砍掉你的构造函数),但在实践中,我希望通过足够的调整来实现。

永远不要这样做。这是丑陋的、令人惊讶的、黑客的和不必要的。标准库的算法已经被设计成可以像对待向量一样对待原始数组。关于这一点,请看其他答案。

评论(3)

除了关于 [tag:c++20] 和 [gsl:span] 中出现的 std::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。引用它的副标题。

span lite - 一个类似于C++20的span,适用于C++98、C++11及以后的单文件头库。

它提供了一个非所有权和可变异的视图(例如,你可以变异元素和它们的顺序,但不能插入它们),而且正如引文所说,它没有依赖性,可以在大多数编译器上工作。

你的例子。


#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)