C++ 为什么移动向量和移动元素对向量有不同的影响';s码?

C++ 为什么移动向量和移动元素对向量有不同的影响';s码?,c++,vector,move,move-semantics,C++,Vector,Move,Move Semantics,我知道size()和empty()不需要先决条件,因此可以对moved from对象调用,并返回正确的结果。但我不明白结果背后的理论 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;

我知道
size()
empty()
不需要先决条件,因此可以对moved from对象调用,并返回正确的结果。但我不明白结果背后的理论

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::vector v={“a”,“b”};
std::string x=std::move(v[0]);
标准::cout
您不会将元素移出集合(在本例中为向量)。您正在将保存在
v[0]
中的对象(一个
std::string
实例)移动到另一个对象
x
。集合本身没有更改

auto y = std::move(v);

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

当移动对象时,您所做的是表示新项将对包含的任何数据或指针承担全部责任,并且旧项将具有不会影响新项的析构函数。目标公司有责任履行这一承诺

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


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

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

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

std::move(…)
获取一个对象并返回一个右值引用。就这样。现在,当创建一个新对象时,我们可以使用该rvalue引用在实际位置调用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 
}
确定通过调用移动构造函数创建新字符串对象。 在此操作之后,v[0]仍然存在,只是v[0]的内部常数发生了变化。所以v.size()是2

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

在向量的move构造函数中,实现者必须执行如下操作 arg.container_size=0;
因为vector的内容有了新的所有者

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

这一切都归结于什么是真正的“移动”。假设我们有一个由3个字符串组成的向量

现在,如果我移动整个向量来表示另一个向量,那么堆上的所有内容都属于moved to vector对象,从moved from vector的角度看不到任何内容

显然,向量的大小变为
0

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


向量的大小仍然是
3

嗯,这是UB。这并不能解决这个问题,但您真的需要
std::endl
所需要的额外内容吗<代码>'\n'
结束一行。如果你搬走了整栋房子,你就剩下一块空地,但如果有人搬出那栋房子里的公寓,公寓仍然在那里。@Vivick:你对一些类有额外的保证,如
std::vector
。From“搬家后,其他的保证是空的()@codekaizer搬家的方法是把整个房子搬走;这比穿过每一个房间,把里面的每一件东西搬到马路对面的新房子里要快。然而,在这个类比中——这不像是搬家,更像是改变你的路名。。。所有东西都保持原样,但它只是被称为不同的东西移动时
v
中的
v[0]
发生了什么?
v[0]
在移动
v
之前和之后并不意味着相同的事情:别忘了
v[0]
实际上是隐藏在sugar中的函数调用。@codekaizer related:@codekaizer您移动的是
v
;它仍然有效。
v[0]
引用的对象没有被移动(或者可能已经移动了-谁知道呢),但是
v
是有效的,它是空的(这实际上是在标准的某处指定的)-导致
v[0]
访问一个不存在的项。@UKMonkey没有那么清楚。它说的是一个移动向量的所有元素都被移动或破坏。当移动时,
v
中的
v[0]
会发生什么?从
v
的角度看-什么都没有。容器不知道对其项所做的操作。@codekaizer您指的是哪一步?移动向量时,包含的
std::string
实例不受影响。它们所在的内存将属于另一个向量,但这与字符串对象无关。当您将实际条目移动到其他地方时,它将执行通常的“获取不确定值”操作。
#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 
}
std::string x = std::move(v[0]);
auto y = std::move(v);