gpt4 book ai didi

c++ - 是什么让 move 对象比复制更快?

转载 作者:IT老高 更新时间:2023-10-28 12:37:05 24 4
gpt4 key购买 nike

我听说 Scott Meyers 说“std::move() 不动任何东西”……但我不明白这是什么意思。

因此,要指定我的问题,请考虑以下事项:

class Box { /* things... */ };

Box box1 = some_value;
Box box2 = box1; // value of box1 is copied to box2 ... ok

关于什么:
Box box3 = std::move(box1);

我确实了解左值和右值的规则,但我不明白的是内存中实际发生了什么?它只是以某种不同的方式复制值,共享地址还是什么?更具体地说:是什么让 move 比复制更快?

我只是觉得理解这一点会让我一切都清楚。提前致谢!

编辑:请注意,我不是在问 std::move()实现或任何语法的东西。

最佳答案

@gudok answered before ,一切都在实现中......然后是用户代码。

实现

让我们假设我们正在讨论为当前类分配值的复制构造函数。

您将提供的实现将考虑两种情况:

  • 参数是一个左值,所以你不能碰它,根据定义
  • 该参数是一个 r 值,因此,隐式地,临时文件在您使用它之后不会存活太久,因此,您可以窃取其内容而不是复制其内容

  • 两者都是使用重载实现的:
    Box::Box(const Box & other)
    {
    // copy the contents of other
    }

    Box::Box(Box && other)
    {
    // steal the contents of other
    }

    轻类的实现

    假设您的类包含两个整数:您不能 那些因为它们是普通的原始值。唯一会 似乎喜欢 偷窃将是复制值,然后将原始值设置为零,或类似的东西......这对于简单的整数没有意义。为什么要做这些额外的工作?

    所以对于轻值类,实际上提供两种特定的实现,一种用于 l 值,一种用于 r 值,是没有意义的。

    仅提供 l 值实现将绰绰有余。

    较重类的实现

    但是在一些重类(即 std::string、std::map 等)的情况下,复制意味着潜在的成本,通常是在分配中。因此,理想情况下,您希望尽可能避免它。这就是 偷窃临时数据变得有趣。

    假设你的 Box 包含一个指向 HeavyResource 的原始指针。复制成本很高。代码变成:
    Box::Box(const Box & other)
    {
    this->p = new HeavyResource(*(other.p)) ; // costly copying
    }

    Box::Box(Box && other)
    {
    this->p = other.p ; // trivial stealing, part 1
    other.p = nullptr ; // trivial stealing, part 2
    }

    很明显,一个构造函数(复制构造函数,需要分配)比另一个( move 构造函数,只需要分配原始指针)慢得多。

    什么时候“偷”是安全的?

    问题是:默认情况下,编译器仅在参数是临时参数时才会调用“快速代码”(这有点微妙,但请耐心等待......)。

    为什么?

    因为编译器可以保证你可以毫无问题地从某个对象中窃取 只有如果该对象是临时对象(或者很快就会被销毁)。对于其他对象,窃取意味着您突然拥有一个有效但处于未指定状态的对象,该对象仍可在代码中进一步使用。可能导致崩溃或错误:
    Box box3 = static_cast<Box &&>(box1); // calls the "stealing" constructor
    box1.doSomething(); // Oops! You are using an "empty" object!

    但有时,您想要性能。你是怎么做到的?

    用户代码

    正如你所写:
    Box box1 = some_value;
    Box box2 = box1; // value of box1 is copied to box2 ... ok
    Box box3 = std::move(box1); // ???

    box2 发生的事情是,因为 box1 是一个左值,所以会调用第一个“慢”复制构造函数。这是正常的 C++98 代码。

    现在,对于 box3,发生了一些有趣的事情:std::move 确实返回相同的 box1,但作为 r 值引用,而不是 l 值。所以这一行:
    Box box3 = ...

    ...不会在 box1 上调用复制构造函数。

    它将在 box1 上调用 INSTEAD 窃取构造函数(正式称为 move 构造函数)。

    由于 Box 的 move 构造函数的实现确实“窃取”了 box1 的内容,因此在表达式的末尾,box1 处于有效但未指定的状态(通常为空),而 box3 包含(前一个) box1 的内容。

    搬出类的有效但未指定的状态怎么样?

    当然,在左值上编写 std::move 意味着您 promise 不再使用该左值。或者你会非常非常小心地去做。

    引用 C++17 标准草案(C++11 是:17.6.5.15):

    20.5.5.15 Moved-from state of library types [lib.types.movedfrom]

    Objects of types defined in the C++ standard library may be moved from (15.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.



    这是关于标准库中的类型,但这是您应该为自己的代码遵循的内容。

    这意味着移出的值现在可以保存任何值,从空值、零值或某个随机值。例如。据您所知,如果实现者认为这是正确的解决方案,您的字符串“Hello”将变成空字符串“”,或者变成“Hell”,甚至“Goodbye”。尽管如此,它仍然必须是一个有效的字符串,并尊重它的所有不变量。

    所以,最后,除非(某种类型的)实现者在 move 后明确 promise 特定行为,否则你应该表现得好像你知道 没什么关于移出的值(那种类型)。

    结论

    如上所述, std::move 什么都不做。它只告诉编译器:“你看到那个 l 值了吗?请把它当作一个 r 值,只是一秒钟”。

    所以,在:
    Box box3 = std::move(box1); // ???

    ...用户代码(即 std::move)告诉编译器该参数可以被视为此表达式的 r 值,因此将调用 move 构造函数。

    对于代码作者(和代码审查者),代码实际上告诉它可以窃取 box1 的内容,将其 move 到 box3 中。然后代码作者必须确保不再使用 box1(或非常小心地使用)。这是他们的责任。

    但最终, move 构造函数的实现会产生影响,主要是在性能上:如果 move 构造函数实际上窃取了 r 值的内容,那么你会看到不同之处。如果它有别的作用,那么作者就撒谎了,但这是另一个问题......

    关于c++ - 是什么让 move 对象比复制更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36827900/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com