【问题标题】:How do vector elements preserve their original address after a vector std::move?向量元素如何在向量 std::move 之后保留其原始地址?
【发布时间】:2019-05-23 02:37:46
【问题描述】:

正如您在输出中看到的那样,向量pre 的对象不仅“移动”到了向量post,而且还保留了它们在内存中的原始地址空间。此举背后的真正原因是什么?这种行为是预期的吗?假设我需要一个单独的指向这些对象的指针向量,是否可以安全地假设在此移动之后对象将始终具有其原始地址?

实际上,我有一个类,其中包含一个像这样的向量和我提到的作为成员的指针向量。我还删除了复制 ctor,并为班级定义了移动。

#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 <------|

【问题讨论】:

    标签: c++ c++11 vector move-semantics


    【解决方案1】:

    向量基本上只是一个指向堆分配内存的指针,向量的当前长度和当前容量。

    通过“移动”一个向量,您所做的就是复制这些值,并重置被移动的向量的值。

    对于向量的数据,基本等价于

    original_pointer = some_place_in_memory;
    new_pointer = original_pointer;   // Copies the *value* of original_pointer
    original_pointer = nullptr;
    

    无需分配新内存和复制向量中的数据。

    【讨论】:

    • 当使用push_back 时,将元素添加到向量中,所有元素的地址都会改变!这是由于向量的设计使其元素在内存中连续存在,因此它将它们复制到另一个(更大的)地址空间?
    • @Vassilis 是的,没错。如果添加元素会使大小超出容量,则必须将数据重新分配到新的更大的内存区域。这就是指向元素的指针和迭代器可能会失效的原因。
    • 是否可以依赖迭代器和指针保持有效(尽管在移动到对象中)
    • 查看手册显然可以依赖它除非源和目标有不同的分配器,在这种情况下,所有元素都单独移动:@ 987654321@ 可能值得添加此信息,因为它是问题的一部分。
    • @Galik,也许你的意思是this,它指的是构造函数。实际上 6 和 7 提到,当使用自定义分配器时,将发生元素移动,因此指向原始对象的指针将无效。
    【解决方案2】:

    移动操作的重点是避免复制元素,因此如果它们被复制(没有真正“移动”内存之类的东西),移动将只是一个副本。

    向量通常实现为 3 个指针:开始、结束和容量。都指向一个动态分配的数组。然后移动向量只是复制这三个指针,因此数组和元素只是改变了它们的所有者。

    我认为假设指向元素的指针仍然有效应该是安全的。

    【讨论】:

    • 为什么容量是指针?我认为这是一个 size_t 变量。
    • @MichaelMahn 也可以。在GCC 中至少它是一个指针。如果您考虑一下,指针是有意义的,因为指针是迭代器,并且在两个迭代器上使用标准算法比在一个迭代器和一个偏移量上使用更容易。
    【解决方案3】:

    很明显,如果我们在没有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));
    

    实际上,向量移动归结为指针复制而不重新分配。指针指向的数据保留在原处,因此向量元素的地址不会改变。

    在第四行之后的这个代码示例中,prepost 都指向相同地址的相同数据。

    std::vector 是一个包含一些附加功能的指向数组的指针的包装器。所以在执行std::vector&lt;B&gt; post(std::move(pre)); 之后,post 将包含一个与pre 中的值相同的指针。

    【讨论】:

      猜你喜欢
      • 2020-04-23
      • 2020-11-02
      • 1970-01-01
      • 2021-11-16
      • 2012-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-09
      相关资源
      最近更新 更多