C++移动语义-其实现的目的是什么?

3

“移动”语义的目的究竟是什么?我知道如果你不通过引用传递非原始类型,就会复制一份副本,但是“移动”如何改变任何东西呢?为什么我们要“移动”数据?为什么不能保持在同一个地址而不进行复制?如果将其发送到另一个地址,这不就是“复制和删除”吗?

简而言之,我真的不明白“移动”语义到底实现了什么。


2
这对我来说是非常好的阅读材料 http://thbecker.net/articles/rvalue_references/section_01.html - Bryan Chen
3个回答

2

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

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.
}

1
这实际上就是移动语义通常所做的。它通常会保持资源(通常是内存,但也可能是文件句柄等)处于完全相同的状态,但它会更新对象中的引用。想象一下两个向量,src 和 dest。src 向量包含一个大块数据,该数据在堆上分配,而 dest 是空的。当 src 移动到 dest 时,所有发生的事情都是将 dest 更新为指向堆上的内存块,同时将 src 更新为指向 dest 所指向的内容,在这种情况下是什么都没有。
为什么这很有用?因为这意味着 vector 可以放心地编写,只有一个 vector 会指向它分配的内存块。这意味着析构函数可以确保清理已分配的内存。
这可以扩展到管理其他资源(例如文件句柄)的对象。现在可以编写可以拥有文件句柄的对象。这些对象可以是可移动的,但不可复制。由于STL容器支持可移动对象,因此这些对象可以更轻松地放入容器中,而这在C++03中则更加困难。文件句柄或其他资源保证只有一个引用,并且析构函数可以适当关闭它。

0
我会用一个简单的向量代数例子来回答:
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-value,它将被 移动v 中,这意味着不会发生额外的潜在巨大数组 data_ 的复制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接