从原始数据就地创建 std::vector

Posted

技术标签:

【中文标题】从原始数据就地创建 std::vector【英文标题】:Create std::vector in-place from raw data 【发布时间】:2017-11-11 09:02:07 【问题描述】:

给定一个原始元素数组,如何创建一个 std::vector 来获取原始数组的所有权,而无需重新分配和复制?

例如拥有原始数组:

int* elems = new int[33]

如何创建一个大小为33std::vector 指向elems

我确信理论上这是可能的,因为通常std::vector 被实现为一个包含三个指针的结构,一个指向分配内存的开头,一个指向有效元素的结尾,一个指向结尾分配的内存。但是有没有用原始数组初始化std::vector 结构的标准方法?

【问题讨论】:

你不能。不要将 new 用于您的“原始数据”,请使用向量。 如果您需要beginfront等常用方法,可以将指向数组的指针存储在unique_ptr<int[]>中,并通过array_view<int>访问数组内容。 做相反的事情:让vector管理内存,并创建一个指向它的指针std::vector<int> v; int* elems = v.data(); 请停止判断/寻找这样做的原因,如果可以,请回答问题;如果您真的需要理由,那么不能更改返回原始指针的外部库,我需要使用 std 将其集成到现代框架中? 通常值得在问题中添加这样的信息,以明确您无法更改原始数组。否则人们只会试图教育你。你可以edit你的问题包含这样的信息。 【参考方案1】:

您需要的是“视图”而不是容器。容器拥有它们的元素,它们的主要目的是封装它们管理的原始内存。如果您需要自己管理内存,那么您不需要容器。看看string_view,如果你有string,那将是你的解决方案。也许boost ranges 是您可以申请的。来自文档(强调我的):

Range 概念的动机是有许多有用的 不满足全部要求的类似容器的类型 容器,以及许多可以用这个简化编写的算法 一套要求。特别是 Range 不一定

拥有可以通过它访问的元素, 具有复制语义,

PS:实际上std::array_view 被考虑用于 C++17,但不幸的是它没有成为标准。

【讨论】:

std::array_view 没有进入 C++17。【参考方案2】:

这不能直接实现的原因是标准库使用分配器为容器预留内存。

因此,如果您有一个std::vector,它使用某种类型的分配器并给它一些您创建的指针,您就有效地打破了分配器的习惯用法。例如,如果您的标准库实现使用mallocfree 而不是newdelete,您的程序将会失败。

为了使其成为标准方式,标准库需要提供一个构造函数,该构造函数接受 T*,该构造函数还必须由向量稍后使用的同一分配器返回。因此,您需要的构造函数的签名类似于std::vector::vector(T* data, size_type used_elements, size_type capacity, const Allocator& alloc)。请注意,分配器参数是必需的,因为 T* 必须(理论上)由向量中使用的完全相同的分配器返回。


您可以通过根据this concept 创建自己的分配器来实现一些功能,但是要使您的33 元素不被重构,您还必须提供@987654331 @ 函数,在 34th 元素(独占)之前是无操作的。此外,您必须首先将向量的大小调整为 33 元素,以强制向量具有正确的大小。

话虽如此,但这并不是一个好主意,因为对于条件构造和分配函数,与复制一次元素相比,您可能会有更多的开销。

【讨论】:

std::vector的声明是template< class T, class Allocator = std::allocator<T> > class vector; ,所以创建的向量可以伴随正确的分配器,包括mallocfree @cDc 是的,但不幸的是我不知道你想用这个告诉我什么.. @nyronium 我认为你可以写一个合法的分配器来做OP想要的;说分配器会检查它初始化的 T* 和大小是否与 arena 内存匹配,并将分配和构造转为几乎无操作(当然,分配器仍然会在增长时复制)... @cDC 当然,我并不是说这是个好主意;与 c-like-apis 交互有很多更好的方法... @MassimilianoJanes 是的,你是对的,但至于 34th 元素 construct 不是空操作,这可能比仅复制数组一次具有更多开销。所以正如你所说,这可能是一个坏主意...... :)【参考方案3】:

根据this,没有构造函数接受指向数据的指针。因此,您不能将原始数组的所有权传递给向量。

您只能创建一个向量并将数据放入其中。

【讨论】:

我知道根据引用的来源没有办法做到这一点,但你不觉得这既可行又有用,应该可以做到吗? @cDc 向量如何安全地获取指针的所有权?没有办法将其限制为动态分配的指针。 不确定你的意思;通过取得所有权,我的意思是 elems 创建 std::vector 后将不再使用变量,并且数组的所有管理都将通过 std::vector 方法完成,包括添加/删除元素。 @cDc:对于std::vector,它没有有用,因为它打破了关于其所有权语义的基本假设。作为另一种工具,它会很有用,比如std::string_view。但是,std::array_view 没有进入 C++17,AFAIK 因为多维数组(?)变得太复杂了。【参考方案4】:

如果您正在处理的对象的类型是可移动的,您可以这样做:

template<typename T>
std::vector<std::unique_ptr<T>> ConvertArrayToVector(T* data, size_t size)

    std::vector<std::unique_ptr<T>> result(size);
    for (unsigned int i = 0; i<size; ++i)
        result[i] = std::make_unique<T>(std::forward<T>(data[i]));

    return result;

生成的向量现在拥有该数组,从某种意义上说,它存储指向其元素的指针并确保在销毁向量时删除对象,但原始数组在此过程中变得无效。

【讨论】:

【参考方案5】:

给定一个原始元素数组,如何创建一个std::vector 无需重新分配和复制即可获得原始数组的所有权?

没有办法。

如何创建一个大小为 33 的 std::vector 指向元素?

不可能。

我相信理论上这是可能的,

不,不是。

但是有没有用原始数组初始化std::vector 结构的标准方法?

没有。


话虽如此,您很有可能可以将解决方案与自定义分配器组合在一起。但是,除了编写自定义分配器是一种很少使用且容易出错的技术这一事实之外,您不应高估这种解决方案的可用性。

std::vector&lt;int&gt;std::vector&lt;int, MyAllocator&gt;两个不同的类。如果您的目标是与需要std::vector&lt;int&gt; 的代码交互,则不能使用std::vector&lt;int, MyAllocator&gt;;如果您打算在代码中创建和使用std::vector&lt;int, MyAllocator&gt;,那么老实说,您最好实现自己的非拥有容器类,即类似自定义VectorView&lt;T&gt;

【讨论】:

以上是关于从原始数据就地创建 std::vector的主要内容,如果未能解决你的问题,请参考以下文章

将 std::vector<int> 从原始内存转换为数组[重复]

为啥 std::vector 的速度是原始数组的两倍?包含完整代码

从 std::vector 在 MEX C++ 中创建 MATLAB 数组

std::vector 调整大小算法

如何从 C++ 中的两个字符串向量创建一个字符串?

std::vector 与 C++ 中的原始数组有多相似?