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

时间:2018-06-15 12:30:17

标签: c++ vector move move-semantics

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

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

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

4 个答案:

答案 0 :(得分:4)

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

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

auto y = std::move(v);

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

答案 1 :(得分:2)

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

在向量中移动对象的情况下,向量不知道对象已被移动;因此,大小不会改变;但是向量所持有的对象现在将是一个可以安全丢弃的“空白”项 - 例如,在unique_ptr的情况下,它将是一个指向null_ptr的ptr。

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

答案 2 :(得分:0)

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

首先,我们必须了解std::move(...)是什么?

std::move(...)获取一个对象并返回一个右值引用。而已。现在,在创建一个新对象时,我们可以使用该右值引用来调用移动构造函数 移动操作发生了。 (便宜的副本)。

让我们看一个例子

#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对象,其中我用新的内存块初始化资源。没什么好看的。

在第二行中,通过传递对象b作为参数来创建新对象a。调用复制构造函数,因此对象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]);

通过调用move构造函数创建新的字符串对象。 在此操作之后,v [0]仍然存在,只有v [0]的内部构造已经改变。所以v.size()是2。

auto y = std::move(v);  

在此操作之后,通过调用move构造函数创建新的矢量对象y

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

答案 3 :(得分:0)

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

这一切都归结为实际的“移动”。假设我们有一个由3个字符串组成的向量。

enter image description here

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

enter image description here

显然,向量的大小变为0

如果我移动一个元素(在这种情况下为字符串),则仅该字符串的数据消失了。向量的数据缓冲区未触及。

enter image description here

向量的大小仍为3