为什么保留额外内存时向量中的对象没有移动?

Why is there no movement of objects in the vector when additional memory is reserved?

有如下代码:

#include <iostream>
#include <vector>

class Test {
   public:
    Test() {}
    Test(int x) : x(x) {}

    Test(const Test&) noexcept = delete;
    Test& operator=(const Test&) noexcept = delete;

    Test(Test&& r) noexcept { x = std::move(r.x); }
    Test& operator=(Test&& r) noexcept {
        std::cout << "move";
        x = std::move(r.x);

        return *this;
    }

    ~Test() noexcept { std::cout << x; }

   private:
    int x;
};

int main() {
    std::vector<Test> v;

    v.reserve(5);
    v.emplace_back(1);
    v.emplace_back(2);
    v.emplace_back(3);
    v.emplace_back(4);
    v.emplace_back(5);
    v.reserve(10);
}

输出:

1234512345

我们看到了析构函数,但没有看到移动赋值,因此我们可以断定对象正在被复制而不是被移动。 这个问题已经在几个话题中讨论过了,但是没有一个具体的答案。

使用 unique_ptrs 有一个解决方法:

#include <iostream>
#include <vector>
#include <memory>

class Test {
   public:
    Test() {}
    Test(int x) : x(x) {}

    Test(const Test&) noexcept = delete;
    Test& operator=(const Test&) noexcept = delete;

    Test(Test&& r) noexcept { x = std::move(r.x); }
    Test& operator=(Test&& r) noexcept {
        std::cout << "move";
        x = std::move(r.x);

        return *this;
    }

    ~Test() noexcept { std::cout << x; }

   private:
    int x;
};

int main() {
    std::vector<std::unique_ptr<Test>> v;

    v.reserve(5);
    v.emplace_back(std::make_unique<Test>(1));
    v.emplace_back(std::make_unique<Test>(2));
    v.emplace_back(std::make_unique<Test>(3));
    v.emplace_back(std::make_unique<Test>(4));
    v.emplace_back(std::make_unique<Test>(5));
    v.reserve(10);
}

输出:

12345

所有代码在编译器 gcc 5.1 - 10.2 上测试

为什么在 vector 中分​​配内存而不是移动时会发生对象复制?为什么第一个代码块中没有使用移动语义,这种行为的原因是什么?

移动constructor/operator 应该使旧对象处于未定义但有效 状态。在这种情况下,编译器选择保持不变并简单地复制值。当这些对象组中的每一个都被破坏时(首先是移动之后,然后是 std::vector 析构函数),它们会打印您看到的值。

语句 v.reserve(10); 使您的向量重新分配其存储空间,因此它可以容纳至少 10 个元素。假设 capacity 小于 10,这是一个四步过程:

  1. 新存储已分配,足够 capacity 至少为 10。

  2. 需要将先前存储中的元素移动到新存储中。对于先前存储中的每个元素,将该元素的值移动到新存储中的新实例中。

  3. 在释放旧存储之前,移出的实例仍然需要销毁。这会导致它们的每个析构函数执行,在您的情况下会打印 12345 。请注意,每个移出的实例仍具有其旧值 x。移动一个int相当于复制它,原值不变

  4. 现在释放了以前的存储空间。矢量现在使用新存储,它包含等效元素但比以前的存储更大。

最后,在 main 结束时,您的向量 v 必须被销毁。这也会导致它的每个元素都被销毁,从而导致每个析构函数都被执行。这是打印的第二组 12345

使用 std::unique_ptr 您只能看到一组数字 (12345),因为 unique_ptr 在两个存储之间移动,而不是 Teststd::unique_ptr<Test> 的新移动构造实例拥有指针的所有权,而旧的移动实例不再拥有这些指针。当先前存储中的实例被销毁时,它们的析构函数什么都不做(因为它们不再拥有指针)。当 vmain 结束时销毁时,您只会看到 Test 实例的销毁。然后,被销毁的 std::unique_ptr 个实例 do 拥有指向 Test 的指针,因此导致 Test 个实例成为 deleted。

编辑:请注意,您只检测了移动赋值运算符。在这种情况下,std::vector 将使用移动构造函数,因此即使 Test 的实例被 v 移动,您也不会看到 "move"