这是关于堆栈内存和堆内存的交互以及通过std::array
和std::vector
类从堆栈到堆的特定情况的问题。
原则上std::array<T>
可以看作是指向第一个元素的指针,加上一些关于数组大小的编译时信息。是否有可能让std::vector<T>
构造函数考虑到这一事实,并尝试通过复制指针将array
的内容移动到vector
。
一个用例是,一个函数返回std::array<double, >
std::array<double, 20> fun(){...};
但后来决定将其分配给std::vector
,而无需逐个元素地复制。
std::vector<double> v = fun(); // not working code
现在必须要做
std::array<double, 20> tmp = fun();
std::vector<double> v(tmp.begin(), tmp.end());
如果可能的话,这实际上是一些多余的工作是不必要的std::vector<double> v(std::move(tmp)); \\ not working code
。
std::vector
和std::array
的内存布局是相同的,所以这不是障碍。
我知道主要的障碍可能是std::array
元素在堆栈中,而std::vector
元素在堆中。很明显,即使有人为std::vector
编写移动构造函数,堆栈中的内存仍然会被不可挽回地破坏。
所以我猜这个问题也可以理解为:
有没有办法将内存从堆栈移到堆中(无论这意味着什么),以及是否可以与移动构造函数结合使用?
或者如果std::vector
可以在原则中有std::array
的移动构造函数吗?
MWE:
#include<array>
#include<vector>
std::array<double, 20> fun(){return {};} // don't change this function
int main(){
std::array<double, 20> arr = fun(); // ok
std::vector<double> v(arr.begin(), arr.end()); // ok, but copies and the allocation is duplicated
std::vector<double> v2 = fun(); // not working, but the idea is that the work is not duplicated
}
答案 0 :(得分:7)
您似乎想告诉std::vector
使用std::array
数据作为其底层缓冲区,至少在需要重新分配之前。
std::vector
没有此接口。它应该自己管理其内部缓冲区,因此以统一的方式分配,跟踪和删除内存。如果你可以提供一个缓冲区来使用,你还需要提供有关它如何分配的信息,是否可能在离开范围时被销毁等等。这很容易出错,因此不可用。
所做的是构建std::vector
std::move_iterator
来将内容移出std::array
。当然,这不会对算术类型产生影响,但对于移动成本低廉的逻辑大对象,可以避免大量数据复制:
std::array<BigThing, 20> a = fun();
std::vector<BigThing> b { std::make_move_iterator(a.begin()),
std::make_move_iterator(a.end())) };
答案 1 :(得分:3)
有没有办法将内存从堆栈移到堆中(无论这意味着什么),是否可以与移动构造函数结合使用?
我个人喜欢“无论那意味着什么”。让我们思考这个问题一段时间。 将某些东西从堆栈移动到堆栈会突然意味着堆栈的那一部分突然被标记为堆分配区域并受到定期破坏。
问题在于堆栈是连续的,并且会被它破坏掉。你不能只说“嘿,留下这个存储位” - 任何连续的堆栈分配和释放都需要跳过“那个部分”。
举例说明:
| |
|----------------------|
| stack block 1 |
|----------------------|
| your vector |
|----------------------|
| stack block 2 |
|----------------------|
|- -|
如果你想要解开那两个块,你需要首先按块2指针的大小减小堆栈指针,然后减去矢量和块1的大小。这真的不是那个可能会发生。
因此,这里唯一可行的解决方案是将副本复制到堆内存区域。但是,这些副本比很多人期望的要快得多。即使向量具有几兆字节,我想,内存控制器只能交换一些页面,而不必物理地发送与数据位相对应的电信号。
此外,任何调整大小的大小都需要导致重新分配。由于数组占用所需的内存,即使添加单个元素也会触发你试图避免的副本。