为什么我可以 std::move 从 const 向量中移动元素?

use*_*931 20 c++ language-lawyer move-semantics

为什么下面的代码可以编译通过?

#include <vector>
#include <iostream>

struct Foo {
  std::vector<int> bar = {1, 2, 3};
};

int main()
{
    Foo foo1;
    const Foo& foo2 = foo1;
    
    std::vector<int> target;
    
    std::move(foo2.bar.begin(), foo2.bar.end(), std::back_inserter(target));

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

std::move的文档说

在此操作之后,移出范围中的元素仍将包含适当类型的有效值,但不一定与移动之前的值相同。

所以这实际上可以改变对象 foo2,即使它被声明为 const。为什么这有效?

And*_*hev 27

所以这实际上可以改变对象 foo2,即使它被声明为 const。为什么这有效?

如果可以的话,允许std::move算法移动输入元素。

对于每个输入元素,它执行*dest = std::move(*from),其中destfrom是输出和输入迭代器。由于from取消引用常量对象,std::move(*from)因此创建右值引用const int&&。由于ints 没有用户定义的构造函数,因此对 的赋值*dest实际上会产生由该语言定义的复制构造。

如果您的元素属于具有用户定义的复制和移动构造函数的类类型T,则重载解析必须选择复制构造函数 ( T(const T&)) 而不是移动构造函数 ( T(T&&)),因为const左值引用可以绑定到const右值,而非const右值引用可以绑定到右值。 t (因为这需要抛弃const)。

底线是std::move(带有迭代器的算法)正在执行移动操作,该操作可能会也可能不会调用移动构造函数或赋值。如果调用移动构造函数或赋值,并且该移动对源具有破坏性,则算法将修改源元素。在其他情况下,它只会执行复制。


Inn*_*der 9

为了用例子来证明 Andrey Semashev 的答案,请考虑以下内容:

#include <vector>

struct movable
{
    movable() = default;
    
    movable(const movable&) = delete;
    movable& operator=(const movable&) = delete;

    movable(movable&&) = default;
    movable& operator=(movable&&) = default;
};

struct copyable
{
    copyable() = default;
    
    copyable(const copyable&) = default;
    copyable& operator=(const copyable&) = default;

    copyable(copyable&&) = delete;
    copyable& operator=(copyable&&) = delete;
};

int main()
{
    // original example
    const std::vector<int> si;
    std::vector<int> ti;
    
    std::move(si.begin(), si.end(), std::back_inserter(ti)); // OK

    // example 2
    const std::vector<copyable> sc;
    std::vector<copyable> tc;
    
    std::move(sc.begin(), sc.end(), std::back_inserter(tc)); // OK

    // example 3
    const std::vector<movable> sv;
    std::vector<movable> tv;
    
    std::move(sv.begin(), sv.end(), std::back_inserter(tv)); // ERROR - tries to use copy ctor

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

即使copyable没有移动构造函数,示例 2 编译时也不会出现错误,因为std::move此处选择了复制构造函数。

另一方面,示例 3 无法编译,因为 的移动构造函数movable被 的常量性否定(更好的词?)sv。您得到的错误是:

error: use of deleted function 'movable::movable(const movable&)'
Run Code Online (Sandbox Code Playgroud)

这是一个完整的例子


更新

分步说明:

  1. 由于我们的类型是const std::vector<T>,所以它的vector::begin()函数返回const_iterator

  2. const_iteratorstd::move当在算法内部取消引用时,返回const T&

  3. std::movestd::move算法内部使用函数,例如:

    error: use of deleted function 'movable::movable(const movable&)'
    
    Run Code Online (Sandbox Code Playgroud)
  4. std::move函数依次返回:

    // taken from cppreference.com
    while (first != last) *d_first++ = std::move(*first++);
    
    Run Code Online (Sandbox Code Playgroud)

    所以,因为const T&它返回const T&&

  5. 由于我们没有构造函数或operator=使用const T&&参数定义,因此重载解析会选择需要的构造函数const T&

  6. 瞧。