如何使std::vector使用已经分配的内存作为其内部数组?

5

有没有一种方法可以将已初始化的数据移动到 std::vector 中?

下面是我自己简单的 vec 类:

template<typename T>
class vec final
{
    std::unique_ptr<T[]> pValues;
    size_t size = 0;
public:
    std::span<T> span() const { return std::span<int>(pValues.get(), size); }
    vec(T* p, size_t sz) : size(sz) { pValues.reset(p); }
};

如您所见,它将接管传递给它的内存:

int main()
{
    constexpr size_t count = 99;
    auto orig = std::make_unique<int[]>(count);
    {
        std::span<int> orig_(orig.get(), count);
        std::iota(orig_.begin(), orig_.end(), -1);
        assert((orig_[0] == -1) && (orig_[1] == 0) && (orig_[98] == 97));
    }

    vec<int> nums(orig.release(), count);
    auto nums_ = nums.span();
    assert((nums_[0] == -1) && (nums_[1] == 0) && (nums_[98] == 97));
}

这一切都可以“按预期”运行,但我希望对std::vector做类似的事情; 特别是,我不想将数据复制到std::vector中(想象一下count比99大得多)。

换句话说,我想要做的是像当我将一个std::vector移动到另一个std::vector时通常会发生的“指针复制”一样;但源是我的自有指针。正如我的示例代码所示,这是“容易”的,但我不想使用自己的vec

完成后,我想在std::vector中进行“流量控制”,因为这样我就可以完全忘记内存管理(并且不必再扩展自己的vec类)。使用std::vector还可以很好地与现有的无法更改的C++代码配合使用。


4
std::vector 无法提供其底层数据指针的访问权限,因此很遗憾不能做到这一点 :-/ - Jarod42
4
如果您不想复制此情况下的数据,则必须首先使用一个向量。然后,您可以将其移动到另一个向量中,这只会复制一些指针。 - Kevin
4
如果符合您的需求,您可以考虑使用 std::span 而不是 std::vector - Nolan
3
@JayBach,std::byte不是int,因此从一个向量移动数据到另一个向量是没有意义的。如果尝试这样做,您将违反别名规则。 - Kevin
2
当替换std::allocator时,“使用已分配的内存作为其内部数组”应该是可能的,但我从未尝试过。所以也许可以求助于谷歌。使用已经填充的数组可能是不可能的。如果std::vector初始化背景内存会怎样呢?猜测您应该尝试替换分配器,然后将向量调整为所需长度,接着将数据填充到共享的内存中。听起来很奇怪,但有可能会起作用。 - schnedan
显示剩余8条评论
1个回答

3

如果您提供一个自定义的分配器,就可以让 std::vector 使用已经分配好的内存:

#include <limits>
#include <iostream>
#include <memory>
#include <vector>
#include <numeric>
#include <cassert>

// allocator adapter to use pre-allocated memory
template <typename T, typename A=std::allocator<T>>
class reuse_mem_allocator : public A {
  typedef std::allocator_traits<A> a_t;

public:
  typedef typename a_t::size_type size_type;
  typedef typename a_t::pointer pointer;

  template <typename U> struct rebind {
    using other =
      reuse_mem_allocator<
        U, typename a_t::template rebind_alloc<U>
      >;
  };

 // have to store a ptr to pre-allocated memory and num of elements
 reuse_mem_allocator(T* p = nullptr, size_type n = 0) throw()
      : p_(p)
      , size_(n)
  { }

  reuse_mem_allocator(const reuse_mem_allocator& rhs) throw()
  : p_(rhs.p_)
  , size_(rhs.size_)
  { }

  // allocate but don't initialize num elements of type T
  pointer allocate (size_type num, const void* = 0) {
    // Unless, it is the first call, and
    // it was constructed with pre-allocated memory.
    if (size_ != 0) {
      if (num == size_) {
        // Then, don't allocate; return pre-allocated mem
        size_ = 0;  // but only once
        return p_;
      } else {
        throw std::bad_alloc();
      }
    } else {
      // allocate memory with global new
      T* ret = (T*)(::operator new(num*sizeof(T)));
      return ret;
    }
  }

  // convert value initialization into default/new initialization
  template <typename U>
  void construct(U* ptr)
    noexcept(std::is_nothrow_default_constructible<U>::value) {
    ::new(static_cast<void*>(ptr)) U;
  }
  
  template <typename U, typename...Args>
  void construct(U* ptr, Args&&... args) {
    a_t::construct(static_cast<A&>(*this),
                   ptr, std::forward<Args>(args)...);
  }
     
  private:
    pointer p_;
    size_type size_;
};


int main()
{
   constexpr size_t count = 9;
   auto orig = std::make_unique<int[]>(count);
   std::iota(orig.get(), orig.get()+count, -1);
   assert((orig[0] == -1) && (orig[1] == 0) && (orig[count-1] == count-2));
       
   std::vector<int, reuse_mem_allocator<int>> num(count, reuse_mem_allocator(orig.release(), count));
   for (auto e : num) {
       std::cout << e << " ";
   }
   std::cout << "\n";
   std::cout << "size: " << num.size() << "\n";
}

我使用C++17编译了它。以下是输出结果:
-1 0 1 2 3 4 5 6 7 
size: 9

分配器适配器是基于这个答案的。


@JayBach,您所说的“兼容”是什么意思?除了移动整个向量之外... - Luis Guzman
1
@JayBach,你说得没错,但是你可以把 f(...) 设计成一个模板:template <typename T> f(const T&)。不过我还会再考虑一下…… - Luis Guzman
1
@JayBach,我编辑了我的答案。我添加了一种将std::vector<int, reuse_mem_allocator<int>>移动到std::vector<int>的方法。希望这有所帮助。 - Luis Guzman
1
这个答案的第一部分(使用分配器)是有效的,也是对问题的很好回答。后半部分(将vector<T, A>的一部分强制转换为vector<T>)是一个可怕的实现特定的hack,会导致未定义行为,不应该被采用。 - Ben Voigt
@BenVoigt,你说得对,编辑部分是实现特定的,只有在分配器放在内存布局的第一位时才有效。我会添加免责声明以明确它是实现特定的。我还会添加一个断言来验证内存布局。或者,我可能会删除这个编辑部分。 - Luis Guzman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接