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



为什么要编译以下代码?

#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;
}

std::move的文档显示

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

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

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

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

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

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

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

要用例子演示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;
}

尽管copyable没有移动构造函数,但示例2编译时没有错误,因为std::move在这里选择了复制构造函数。

另一方面,示例3未能编译,因为movable的move构造函数被sv的常量否定(更好的词?(。你得到的错误是:

error: use of deleted function 'movable::movable(const movable&)'

下面是一个完整的例子。


更新

分步说明:

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

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

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

    // taken from cppreference.com
    while (first != last) *d_first++ = std::move(*first++);
    
  4. std::move函数依次返回:

    // taken from cppreference.com
    static_cast<typename std::remove_reference<T>::type&&>(t)`
    

    因此,对于const T&,它返回const T&&

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

  6. 沃伊拉。

最新更新