关于c ++ 11:C ++move语义 – 究竟要实现什么?

C++ move semantics- what exactly is it to achieve?

本问题已经有最佳答案,请猛点这里访问。

这个"移动"语义的确切目的是什么?我理解如果你不通过引用传递一个副本是由非原语类型构成的,但是"移动"是如何改变任何东西的?为什么我们要"移动"数据?为什么不能把它放在同一个地址而不复制呢?如果它被发送到另一个地址,这不是一个"复制和删除"吗?

简言之,我真的不知道移动语义到底实现了什么。


移动语义结合了按值传递和按引用传递的优点。您可以静态地分配类,这样就不必为它们的生命周期负责,并且可以将它们作为参数传递并轻松地从函数返回。另一方面,在这种情况下,当通常复制对象时,它们会被移动(仅复制其内部)。此操作的实现时间可能比复制要短得多(因为您知道,不会再使用rhs对象)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
MyObj * f()
{
    // Ok, but caller has to take care of
    // freeing the result
    return new MyObj();
}

MyObj f()
{
    // Assuming, that MyObj does not have move-ctor
    // This may be time-costly
    MyObj result;
    return result;
}

MyObj f()
{
    // This is both fast and safe
    MyObj result;
    return std::move(result);

    // Note, if MyObj implements a move-ctor,
    // usually you don't have to call std::move.
}

Why cant it just be kept at the same address and not copied

这实际上就是移动语义通常所做的。它通常将资源(通常是内存,但可能是文件句柄等)保持在完全相同的状态,但它会更新对象中的引用。

想象两个向量,srcdestsrc向量包含一个在堆上分配的大数据块,dest为空。当src被移到dest时,所发生的一切就是dest被更新为指向堆上的内存块,而src被更新为指向dest所指的任何对象,在这种情况下,都不是。

为什么这个有用?因为这意味着vector可以放心地写入,只有一个向量会指向它分配的内存块。这意味着析构函数可以确保它清理已分配的内存。

这可以扩展到管理其他资源的对象,如文件句柄。现在可以编写可以拥有文件句柄的对象。这些对象可以移动,但不可复制。因为STL容器支持可移动对象,所以这些容器可以比C++ 03中的容器容易得多。它们的文件句柄或其他资源保证只有对它的引用,并且析构函数可以适当地关闭它。


我用向量代数的一个简单例子来回答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Vector{
  size_t dim_;
  double *data_;
public:
  Vector(const Vector &arg)
    : dim_(arg.dim_)
    , data_(new double[dim_])
  {
    std::copy_n(arg.data_, dim_, data_);
  }

  Vector(Vector &&arg)
    : dim_(arg.dim_)
    , data_(arg.data_)
  {
    arg.data_ = nullptr;
  }

  ~Vector()
  {
    delete[] data_;
  }

  Vector& operator+= (const Vector &arg)
  {
    if (arg.dim_ != dim_) throw error;
    for (size_t idx = 0; idx < dim_; ++idx) data_[idx] += arg.data_[idx];
    return *this;
  }
};

Vector operator+ (Vector a, const Vector &b)
{
  a += b;
  return a;
}

extern Vector v1, v2;

int main()
{
  Vector v(v1 + v2);
}

加法按值返回一个新的向量。因为它是一个r值,所以它将被移到v中,这意味着不会再出现可能巨大阵列data_的额外副本。