向量元素如何在向量 std::move 之后保留其原始地址?
How do vector elements preserve their original address after a vector std::move?
正如您在输出中看到的那样,向量 pre
的对象不仅 "moved" 到向量 post
,而且还保留了它们的原始地址 space在记忆中。此举背后究竟发生了什么?这种行为是预期的吗?假设我需要一个单独的指向这些对象的指针向量,是否可以安全地假设在此移动之后对象将始终具有其原始地址?
实际上,我有一个 class 包含一个像这样的向量和我作为成员提到的指针向量。我还删除了复制代码,并为 class.
定义了移动代码
#include <iostream>
#include <vector>
struct B {
int val = 0;
B(int aInt) : val(aInt) { };
};
int main() {
std::vector<B> pre;
pre.push_back(B(1));
pre.push_back(B(2));
std::cout << "pre-move:\t" << (void*)&pre.at(0) << '\n';
std::cout << "pre-move:\t" << (void*)&pre.at(1) << '\n';
std::vector<B> post(std::move(pre));
std::cout << "post-move:\t" << (void*)&post.at(0) << '\n';
std::cout << "post-move:\t" << (void*)&post.at(1) << '\n';
return 0;
}
输出:
pre-move: 0x1d7b150
pre-move: 0x1d7b154 <------|
post-move: 0x1d7b150 |
post-move: 0x1d7b154 <------|
很明显,如果我们在没有 std::vector
的情况下编写语义相同的代码:
B* pre = new B[2]; // Declare std::vector<B> and allocate some space to make the following line correct
B[0] = 1; // pre.push_back(B(1));
B[1] = 2; // pre.push_back(B(2));
B* post = pre; // std::vector<B> post(std::move(pre));
实际上,矢量移动归结为指针复制而不重新分配。指针指向的数据保留在原处,因此向量元素的地址不会改变。
在第四行之后的代码示例中,pre
和post
都指向具有相同地址的相同数据。
std::vector
是指向数组的指针的包装器,具有一些附加功能。所以在执行 std::vector<B> post(std::move(pre));
之后,post
将包含一个与 pre
.
中具有相同值的指针
向量基本上无非是一个指向堆分配内存的指针,向量的当前长度和当前容量。
通过 "moving" 向量,您所做的只是复制这些值,并重置移出向量的值。
对于vector的数据,基本等同于
original_pointer = some_place_in_memory;
new_pointer = original_pointer; // Copies the *value* of original_pointer
original_pointer = nullptr;
无需分配新内存和复制向量中的数据。
移动操作的重点是避免复制元素,所以如果它们被复制(没有真正的 "moving" 内存)移动将只是一个副本。
向量通常实现为 3 个指针:begin、end 和 capacity。都指向一个动态分配的数组。然后移动向量只是复制这三个指针,所以数组和元素只是改变它们的所有者。
我认为假设指向元素的指针仍然有效应该是安全的。
正如您在输出中看到的那样,向量 pre
的对象不仅 "moved" 到向量 post
,而且还保留了它们的原始地址 space在记忆中。此举背后究竟发生了什么?这种行为是预期的吗?假设我需要一个单独的指向这些对象的指针向量,是否可以安全地假设在此移动之后对象将始终具有其原始地址?
实际上,我有一个 class 包含一个像这样的向量和我作为成员提到的指针向量。我还删除了复制代码,并为 class.
定义了移动代码#include <iostream>
#include <vector>
struct B {
int val = 0;
B(int aInt) : val(aInt) { };
};
int main() {
std::vector<B> pre;
pre.push_back(B(1));
pre.push_back(B(2));
std::cout << "pre-move:\t" << (void*)&pre.at(0) << '\n';
std::cout << "pre-move:\t" << (void*)&pre.at(1) << '\n';
std::vector<B> post(std::move(pre));
std::cout << "post-move:\t" << (void*)&post.at(0) << '\n';
std::cout << "post-move:\t" << (void*)&post.at(1) << '\n';
return 0;
}
输出:
pre-move: 0x1d7b150
pre-move: 0x1d7b154 <------|
post-move: 0x1d7b150 |
post-move: 0x1d7b154 <------|
很明显,如果我们在没有 std::vector
的情况下编写语义相同的代码:
B* pre = new B[2]; // Declare std::vector<B> and allocate some space to make the following line correct
B[0] = 1; // pre.push_back(B(1));
B[1] = 2; // pre.push_back(B(2));
B* post = pre; // std::vector<B> post(std::move(pre));
实际上,矢量移动归结为指针复制而不重新分配。指针指向的数据保留在原处,因此向量元素的地址不会改变。
在第四行之后的代码示例中,pre
和post
都指向具有相同地址的相同数据。
std::vector
是指向数组的指针的包装器,具有一些附加功能。所以在执行 std::vector<B> post(std::move(pre));
之后,post
将包含一个与 pre
.
向量基本上无非是一个指向堆分配内存的指针,向量的当前长度和当前容量。
通过 "moving" 向量,您所做的只是复制这些值,并重置移出向量的值。
对于vector的数据,基本等同于
original_pointer = some_place_in_memory;
new_pointer = original_pointer; // Copies the *value* of original_pointer
original_pointer = nullptr;
无需分配新内存和复制向量中的数据。
移动操作的重点是避免复制元素,所以如果它们被复制(没有真正的 "moving" 内存)移动将只是一个副本。
向量通常实现为 3 个指针:begin、end 和 capacity。都指向一个动态分配的数组。然后移动向量只是复制这三个指针,所以数组和元素只是改变它们的所有者。
我认为假设指向元素的指针仍然有效应该是安全的。