【问题标题】:copy vs std::move for ints复制 vs std::move 用于整数
【发布时间】:2015-03-09 10:41:00
【问题描述】:
  • 在该示例中,默认副本和 std::move 有什么区别?
  • move之后的对象,新旧对象之间是否存在依赖关系?
int main () {

    int a = 100;
    std::cout<<&a<<std::endl;

    auto a_copy = a;                 // deduced as int
    std::cout<<&a_copy<<std::endl;

    auto a_move = std::move(a);      // deduced as int
    std::cout<<&a_move<<std::endl;

};

输出:

0x7fffffffe094
0x7fffffffe098
0x7fffffffe09c

【问题讨论】:

  • 请注意std::move 实际上并没有移动任何东西。它只是对右值 ref 执行强制转换,结果是一个右值,而简单地命名 a 则不是。这两个事实是提供一个移动对象的一部分,但实际上并不是从它移动的东西。因此,询问“move 之后”是一个转移话题。

标签: c++ c++11 move-semantics


【解决方案1】:

this 示例中,没有区别。我们最终会得到 3 个ints,值为 100。不过,不同类型肯定会有所不同。例如,让我们考虑类似vector&lt;int&gt;

std::vector<int> a = {1, 2, 3, 4, 5}; // a has size 5
auto a_copy = a;                      // copy a. now we have two vectors of size 5
auto a_move = std::move(a);           // *move* a into a_move

最后一个变量a_move 拥有a 的内部指针。所以我们最终得到的是 a_move 是一个大小为 5 的向量,但 a 现在是空的。 movecopy 高效得多(想象一下,如果它是 1000 个字符串的向量 - a_copy 将涉及分配 1000 个字符串的缓冲区并复制 1000 个字符串,但 a_move 只分配几个指针)。

对于其他一些类型,其中一个可能是无效的:

std::unique_ptr<int> a{new int 42};
auto a_copy = a;            // error
auto a_move = std::move(a); // OK, now a_move owns 42, but a points to nothing

对于许多类型,但没有区别:

std::array<int, 100> a;
auto a_copy = a;            // copy 100 ints
auto a_move = std::move(a); // also copy 100 ints, no special move ctor

更一般地说:

T a;
auto a_copy = a;            // calls T(const T& ), the copy constructor
auto a_move = std::move(a); // calls T(T&& ), the move constructor

【讨论】:

  • @Person.Junkie 是的,std::move 只是对右值 ref 执行静态强制转换,但绝对更喜欢 std::move() 而不是编写强制转换 - 它 (a) 键入更少并且 (b) 使意图代码行更清晰。
  • @Barry:我不同意——虽然它要短得多,但它是一个谎言
  • @LightnessRacesinOrbit 您希望看到std::move 重命名为std::rref 之类的名称吗?因为我知道我会 - std::move 极具误导性。
  • @Barry:是的,我们开始了;这大概就是我所记得的。我相信这一点已经被各种高调类型承认了几次(而不是仅限于 cprogramming.com 上的论坛帖子)。也就是说,“它表示意图”是一个可爱的原则,它可能在简单的脚本语言中运行良好,但它在像 C++ 这样灵活的东西中完全违背了逻辑(更不用说既定的范式了)。
  • 向量a移动后不保证为空。
【解决方案2】:

使用std::move 只是将左值更改为xvalue,因此它可以与移动构造函数和移动赋值运算符一起使用。这些对于内置类型不存在,因此在此示例中使用 move 没有区别。

【讨论】:

    【解决方案3】:

    在该示例中,默认副本和 std::move 有什么区别?

    没有区别。复制某物满足移动的要求,在内置类型的情况下,移动被实现为复制。

    移动对象后,新旧之间是否存在依赖关系

    不,没有依赖关系。两个变量都是独立的。

    【讨论】:

    • ...std::move 有什么优势,为什么?
    • @Person.Junkie 在这里?没有优势。缺点是代码比较混乱。
    • 一般?为什么存在 std::move?我知道 std::move 使用 static_cast 将左值/右值转换为右值,这种“强制转换”能带来很大的改进吗?
    • @Person.Junkie 这太宽泛了。有整篇关于这方面的论文。但基本上,有些类型移动起来很便宜,复制起来也很昂贵。在这些情况下,移动可能会有优势。此外,有些东西是不可复制的,但可能是可移动的(例如文件流或唯一指针。)
    • @Person.Junkie:演员阵容本身并没有带来任何“大的进步”。转换允许程序员方便地表明他们想要调用一个不同的函数来实现数据传输:传统上,我们编写移动而不是复制的“移动构造函数”,例如通过交换内部指针。这可能会带来巨大的改进,但std::move 本身并没有这样做。你可以在 C++03 中使用标签和其他各种技术来实现,只是不太方便(并且不能通用,例如标准库容器中的元素)。
    【解决方案4】:

    为了扩展其他发帖人的答案,移动即复制范式也适用于由 POD 类型(或由 POD 类型组成的其他类型)组成的所有数据结构,如下例所示:

    struct Foo
    {
        int values[100];
        bool flagA;
        bool flagB;
    };
    
    struct Bar
    {
        Foo foo1;
        Foo foo2;
    };
    
    int main()
    {
        Foo f;
        Foo fCopy = std::move(f);
        Bar b;
        Bar bCopy = std::move(b);
        return 0;
    }
    

    FooBar 的情况下,没有有意义的方式将数据从一个移动到另一个,因为两者最终都是 POD 类型的聚合 - 它们的数据都不是间接拥有的(指向或引用其他记忆)。因此,在这些情况下,移动是作为副本实现的,而原件(fb)在 std::move() 行上的分配之后保持不变。

    移动语义只能通过动态分配的内存或独特的资源来有意义地实现。

    【讨论】:

    • “没有有意义的方式将数据从一个移动到另一个,因为两者都是由值类型组成的”——我不认为值类型是正确的术语这里。所有标准库容器都是值类型,当然有一种有意义的方式来移动它们的数据。
    • @BenjaminLindley 虽然它们不是由值类型组成 - 它们包含动态分配的内存(因此,指针类型)。
    • 好的,但是如果我将std::vector 放在一个结构中,像这样:struct Foo { std::vector&lt;int&gt; v; };,那么该结构是由值类型组成的,并且有一种有意义的方式来移动它的数据。
    • @BenjaminLindley 我认为您对此过于迂腐 - 它仅由表面上的值类型组成,而不是整个组合链。
    • 我喜欢称其为“平面数据”,但我认为这是我编造的。当然,重点是不涉及间接性,所以如果你能以某种方式将其应用到语言中,你就可以排序了。
    猜你喜欢
    • 1970-01-01
    • 2017-05-28
    • 2019-07-09
    • 1970-01-01
    • 1970-01-01
    • 2020-01-17
    • 1970-01-01
    • 2017-11-18
    • 2015-03-28
    相关资源
    最近更新 更多