为什么移动向量和移动元素对向量的大小有不同的影响?

Why do moving vector and moving element have different effect on vector's size?

我知道 size()empty() 不需要前置条件,因此可以在移出的对象上调用并且 return 正确的结果。但我不明白结果背后的理论。

std::vector<std::string> v = {"a", "b"};
std::string x = std::move(v[0]);
std::cout << x << std::endl;          // a
std::cout << v.size() << std::endl;   // 2
std::cout << v.empty() << std::endl;  // 0, false
auto y = std::move(v);                
std::cout << v.size() << std::endl;   // 0
std::cout << v.empty();               // 1, true

结果显示,如果移动元素,向量的大小不会改变。但是如果你移动整个向量,它就会变成空的。有道理,但我觉得需要更多的解释,以便我以后处理类似的情况。

std::string x = std::move(v[0]);

您没有将元素移出集合(在本例中为矢量)。您正在将一个对象 - 一个 std::string 实例 - 保存在 v[0] 中移动到另一个对象 x。集合本身没有改变。

auto y = std::move(v);

这会将 std::vector 对象 v 移动到 y 对象中。

当你移动一个对象时,你所做的就是说新项目将对包含的任何数据或指针负全部责任,而旧项目将有一个不会影响新项目的析构函数。对象有责任实现这个承诺。

当你移动vector中的对象时,vector并不知道对象被移动了;因此大小不会改变;但是 vector 持有的对象现在将是一个可以安全丢弃的 'blank' 项目 - 例如在 unique_ptr 的情况下,它将是一个指向 null_ptr 的 ptr .

移动向量做完全相同的事情 - 它将向量中的所有项目移动到新的项目 - 然后清除自身以确保在其析构函数上它不会影响它持有的项目。

好的,我会尽力解释,如果您发现任何错误,请原谅我。

首先我们要明白什么是std::move(...) ?

std::move(...) 接受一个对象和 returns 一个右值引用。而已。现在,当创建一个新对象时,我们可以使用该右值引用来调用实际位置的移动构造函数 移动操作发生。 (便宜的副本)。

让我们看一个例子

#include <iostream>
#include <cstring>
#include <cstdlib>

using std::cout;

struct foo
{
    int *resource;

    foo() 
    : resource{new int[10]} 
    {
        cout << "c'tor called\n";
    }

    foo(const foo &arg) 
    :foo{}  // delegating constructors. calls above constructor first then execute my body. 
    {

        /* Here expensive copy happens */

        memcpy(resource, arg.resource, 10);
        cout << "copy c'tor called\n";
    }

    foo(foo &&arg) 
    : resource{arg.resource}  // just change ownership of resource. 

    {
        /* 
            Here actual move happens. 
            Its implementator's job.
        */

        cout << "move c'tor called\n";
        arg.resource = nullptr;
    }
};


int main()
{
    foo a{};
    foo b{a};               // calls copy constructor


    /*
        Do some stuff with a and b
    */

    foo c{std::move(a)}     // calls move constructor 
}

我在这里创建了 foo a 对象,我在其中使用新的内存块初始化资源。这里没什么特别的。

在第二行中,通过将对象 a 作为参数传递来创建新对象 b。复制构造函数被调用,结果对象 b 与对象 a.

相同

现在我想创建另一个应该与 a 相同的对象,同时我知道我不会再使用对象 a,所以我 foo c{std::move(a)} .

我也可以做到 foo c{a},但我知道我不会再使用 a,所以交换对象的内容非常有效。

在移动构造函数中,我需要确保这样做 arg.resource = nullptr。如果我不这样做,并且如果有人不小心更改了对象 a,这也会间接影响对象 c

完成后对象 a 仍然有效并且存在。只是内容变了。现在来提问

std::string x = std::move(v[0]);

好的,通过调用移动构造函数创建新的字符串对象。 此操作后 v[0] 仍然存在,只是 v[0] 的内部内容发生了变化。所以 v.size() 是 2.

auto y = std::move(v);  

在此操作之后,通过调用移动构造函数创建了新的向量对象 y

在 vector 的移动构造函数中,实现者必须做这样的事情 arg.container_size = 0; 因为 vector 的内容有了新的所有者。

我想自己回答这个问题,因为我认为一些图表可以帮助人们(包括我)更容易地理解这个问题。

一切都归结为 "moved"。假设我们有一个包含 3 个字符串的向量。

现在,如果我将整个向量移动到另一个向量,那么堆上的所有内容都归移入向量对象所有,从移出向量的角度来看,什么也看不到。

显然,向量的大小变为0

如果我移动一个元素,在这种情况下是一个字符串,那么只有字符串的数据会消失。向量的数据缓冲区未被触及。

矢量的大小仍然是 3