【问题标题】:Why do moving vector and moving element have different effect on vector's size?为什么移动向量和移动元素对向量的大小有不同的影响?
【发布时间】:2018-11-25 07:17:54
【问题描述】:

我知道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

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

【问题讨论】:

  • 嗯,这是UB。
  • 这并没有解决问题,但是您真的需要std::endl 需要的额外内容吗? '\n' 结束一行。
  • 如果你搬了整栋房子,你会留下一块空地,但如果有人搬出那栋房子的公寓,公寓还在。
  • @Vivick:你有一些额外的保证,比如std::vector。来自doc“移动后,其他保证为空()。”
  • @codekaizer 搬家的方法是搬家;这比遍历每个房间,然后将其中的每件物品搬到路上的新房子还要快。不过,在这个类比中 - 它不像搬家,更像只是改变你的道路名称......一切都保持原样,但它只是被称为不同的东西

标签: c++ vector move move-semantics


【解决方案1】:
std::string x = std::move(v[0]);

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

auto y = std::move(v);

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

【讨论】:

  • 移动时v 中的v[0] 会发生什么情况?
  • v 的角度来看 - 没有。容器不知道对其项目进行的操作。
  • @codekaizer 你指的是哪一步?移动向量时,包含的 std::string 实例不受影响。它们所在的内存将由不同的向量拥有,但这与字符串对象无关。当您在其他地方move 实际输入v[0] 时,它会执行通常的“获取不确定值”的操作。
【解决方案2】:

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

当你在vector中移动物体的时候,vector并不知道物体已经被移动了;因此尺寸不会改变;但是向量所持有的对象现在将是一个可以安全丢弃的“空白”项目 - 例如,在 unique_ptr 的情况下,它将是一个指向 null_ptr 的 ptr。

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

【讨论】:

  • 移动时v 中的v[0] 会发生什么情况?
  • v[0] 并不意味着 beforeafter v 被移动:不要忘记 v[0] 是实际上是隐藏在糖中的函数调用。
  • @codekaizer 你移动的是v;它仍然有效。 v[0] 所指的对象尚未移动(或者它可能已经移动 - 谁知道)但是虽然 v 有效,但它是空的(这实际上是在某处的标准中指定的) - 导致 v[0] 访问不存在的项目。
  • @UKMonkey [container.requirements/4] 不是很清楚。它的意思是移动矢量的所有元素都被移动或销毁。
【解决方案3】:

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

首先我们要了解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]);

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

auto y = std::move(v);  

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

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

【讨论】:

    【解决方案4】:

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

    这一切都归结为实际“移动”的内容。假设我们有一个包含 3 个字符串的向量。

    现在,如果我将整个向量移动到另一个向量,那么堆上的所有内容都归被移动到的向量对象所有,从被移动到的向量的角度看不到任何东西。

    很明显,向量的大小变成了0

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

    向量的大小仍然是3

    【讨论】:

      猜你喜欢
      • 2012-09-18
      • 2016-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多