能否将std::array移动到std::vector中?

9
这是关于堆栈内存和堆内存交互的问题,特别是通过 std::arraystd::vector 类从堆栈到堆的情况。
原则上,std::array<T> 可以被视为指向第一个元素的指针,再加上一些编译时有关数组大小的信息。是否可能有一个 std::vector<T> 构造函数,考虑到这一点,并尝试通过复制指针来将 array 的内容移动到 vector 中。
一个使用案例是,有一个返回 std::array<double, > 的函数: std::array<double, 20> fun(){...}; 但后来决定将其分配给一个 std::vector,而不需要逐个复制元素。 std::vector<double> v = fun(); // 不起作用的代码 现在,必须执行以下操作:
std::array<double, 20> tmp = fun();
std::vector<double> v(tmp.begin(), tmp.end());

如果这是可能的话,std::vector 的移动构造函数就不需要做一些冗余的工作了,例如:std::vector<double> v(std::move(tmp)); \\ 这段代码无法正常工作

std::vectorstd::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
}

如果您将数组移动到向量中,然后使用push_back()添加一个新元素会怎样? - Antonio Pérez
@AntonioPérez,说得好,但我猜std::vector会像通常一样行事并重新分配(保留)更多内存(可能在内存的其他位置)。优化将丢失,但这不是问题的情况。 - alfC
也许使用“小向量优化”对于使用vector类的非标准容器来说是有限制的。http://www.boost.org/doc/libs/1_60_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.small_vector。据我所知,这仍然不包括不使用此优化的`std::vector`(但`std::basic_string`使用)。也许还有一种特定(虚假)分配器的`std::vector`的某种专门化,就像下面的评论之一。 - alfC
2个回答

9
似乎您想让std::vector使用std::array数据作为其底层缓冲区,至少在需要进行一些重新分配之前是这样的。 std::vector没有此接口。它应该自己管理其内部缓冲区,因此内存以统一的方式分配、跟踪和删除。如果您可以提供要使用的缓冲区,则还需要提供有关如何分配它、是否可能在离开作用域时被销毁等信息。这很容易出错且不美观,因此不可用。
您可以使用std::move_iterator构建std::vector,将内容从std::array中移出。当然,对于算术类型,这不会有任何区别,但对于逻辑上大而移动成本低的对象,它可以避免大量数据复制。
std::array<BigThing, 20> a = fun();
std::vector<BigThing> b { std::make_move_iterator(a.begin()),
                          std::make_move_iterator(a.end())) };

3
你可以使用自定义的分配器,例如Howard Hinnant的short_alloc。我看到的主要问题是vector管理其自己的大小,你需要一些技巧来使用已经填充好的数组进行初始化(例如使用resize和一个自定义的allocator::construct,在请求value-init时不执行任何初始化)。 - dyp
@dyp,是的,我考虑过自定义分配器,它将从提供的数组中模拟内存分配。问题(也许是不可避免的)是,在任何时候(取决于作用域),std::array本身都可能消失。(我没有提到自定义分配器黑客来保持问题简单)。一种选择是以某种方式使数组析构函数调用“消失”,但即使这是可能的,它也会违反堆栈行为的某些限制(请参见@Bartek的答案)。 - alfC
我不知道move_iterator的存在 http://en.cppreference.com/w/cpp/iterator/move_iterator - alfC
@dyp 这是否可能而不违反分配器的合同? - TartanLlama
1
找到了:https://dev59.com/qWEi5IYBdhLWcg3wseGA#21028912/ 另请参阅:https://dev59.com/WHDYa4cB1Zd3GeqPE9pN#15975738/ - dyp
显示剩余3条评论

5
有没有一种方法可以将内存从栈移动到堆(不管那是什么),并且是否可以与移动构造函数结合使用?
个人认为“不管那是什么”这部分很有趣。让我们思考一下。将某个东西从栈移到堆意味着该部分栈突然变成了一个堆分配的区域,并且需要定期销毁。
问题在于栈是连续的,并且通过弹出它来销毁。你不能只说“嘿,留下这块内存位”,任何连续的栈分配和释放都需要跳过那部分。
为了说明:
|                      |
|----------------------|
| stack block 1        |
|----------------------|
| your vector          |
|----------------------|
| stack block 2        |
|----------------------|
|-                    -|

如果你想要解开这两个块,你需要先将堆栈指针减少块2指针的大小,然后再减去向量和块1的大小。但这不是可能发生的事情。
因此,这里唯一可行的解决方案是将其复制到堆内存区域中。然而,那些副本比很多人预期的要快得多。即使向量有几兆字节,内存控制器也可以只是交换一些页面,我想,并不需要物理上发送与数据位相对应的电信号。
此外,任何向量的调整大小都需要引起重新分配。由于数组占用恰好所需的内存,即使添加一个元素也会触发你试图避免的复制。

很棒的答案。我想一切都可以归结为“堆栈和堆只能通过复制进行通信”。有趣的细节是,在使用情况下,数组的内存位于堆栈顶部,同时被传输(复制或移动)到std::vector中。 - alfC
是的,如果向量后来增长,则优化将丧失,因为内存将需要重新分配。但仅在此情况下才是如此,如果它保持不变,则仍然可以。 - alfC
1
即使向量有几兆字节,内存控制器也只需交换一些页面,我想,并不必物理地发送与数据位相对应的电信号。我不这么认为。当向量开始复制位时,页面已经被malloc(或类似函数)分配,并且操作系统不知道在保留这些页面时它们最终会包含堆栈的某个部分相同的数据。您还假设对齐到页面边界,并将几兆字节放在堆栈上。 - Jonathan Wakely
1
也可以有非连续的堆栈(搜索“分割堆栈”或“分段堆栈”),但在这种情况下并没有什么帮助。 - Jonathan Wakely

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