11

可能重复:
有人可以向我解释一下移动语义吗?

有人可以指出一个好的来源或在这里解释什么是移动语义?

4

4 回答 4

35

暂时忘记 C++0x。移动语义是与语言无关的东西——C++0x 仅提供了一种使用移动语义执行操作的标准方法。

定义

移动语义定义了某些操作的行为。大多数情况下,它们与复制语义形成对比,因此首先定义它们会很有用。

具有复制语义的赋值具有以下行为:

// Copy semantics
assert(b == c);
a = b;
assert(a == b && b == c);

a最终等于b,我们b保持不变。

具有移动语义的赋值具有较弱的后置条件:

// Move semantics
assert(b == c);
move(a, b); // not C++0x
assert(a == c);

请注意,在使用移动语义进行赋值之后,不再有任何保证b保持不变。这是关键的区别。

用途

移动语义的一个好处是它允许在某些情况下进行优化。考虑以下常规值类型:

struct A { T* x; };

还假设我们定义两个类型A为相等的对象,如果它们的x成员指向相等的值。

bool operator==(const A& lhs, const A& rhs) { return *lhs.x == *rhs.x; }

最后假设我们定义一个对象A对其x成员的指针拥有唯一的所有权。

A::~A() { delete x; }
A::A(const A& rhs) : x(new T(rhs.x)) {}
A& A::operator=(const A& rhs) { if (this != &rhs) *x = *rhs.x; }

现在假设我们要定义一个函数来交换两个A对象。

我们可以使用复制语义以正常方式进行操作。

void swap(A& a, A& b)
{
    A t = a;
    a = b;
    b = t;
}

然而,这是不必要的低效。我们在做什么?

  • a我们创建into的副本t
  • 然后我们复制ba.
  • 然后复制tb.
  • 最后,销毁t

如果T对象的复制成本很高,那么这是一种浪费。如果我让您在计算机上交换两个文件,您不会创建第三个文件然后在销毁临时文件之前复制并粘贴文件内容,对吗?不,您将移开一个文件,第二个文件移到第一个位置,然后最后第一个文件移回第二个位置。无需复制数据。

在我们的例子中,很容易在 type 对象周围移动A

// Not C++0x
void move(A& lhs, A& rhs)
{
    lhs.x = rhs.x;
    rhs.x = nullptr;
}

我们只需将rhs' 指针移入lhs然后放弃rhs该指针的所有权(通过将其设置为 null)。这应该说明为什么移动语义的较弱后置条件允许优化。

通过定义这个新的移动操作,我们可以定义一个优化的交换:

void swap(A& a, A& b)
{
    A t;
    move(t, a);
    move(a, b);
    move(b, t);
}

移动语义的另一个优点是它允许您在无法复制的对象周围移动。一个典型的例子是std::auto_ptr

C++0x

C++0x 通过其右值引用特性允许移动语义。具体来说,此类操作:

a = b;

b当是右值引用(拼写)时具有移动语义T&&,否则它们具有复制语义。当不是右值引用时,您可以使用std::move函数(与move我之前定义的不同)强制移动语义:b

a = std::move(b);

std::move是一个简单的函数,它本质上将其参数转换为右值引用。请注意,表达式(例如函数调用)的结果自动是右值引用,因此您可以在这些情况下利用移动语义而无需更改代码。

要定义移动优化,您需要定义移动构造函数和移动赋值运算符:

T::T(T&&);
T& operator=(T&&);

由于这些操作具有移动语义,您可以自由修改传入的参数(前提是您将对象置于可破坏状态)。

结论

这基本上就是它的全部内容。请注意,右值引用也用于在 C++0x 中允许完美转发(由于右值引用和其他类型之间专门设计的类型系统交互),但这与移动语义无关,所以我没有讨论在这里。

于 2011-07-25T18:13:31.990 回答
4

基本上,右值引用允许您检测对象何时是临时对象并且您不必保留它们的内部状态。这使得 C++03 过去必须一直复制的代码更加高效,而在 C++0x 中,您可以继续重复使用相同的资源。此外,右值引用可以实现完美转发。

看看这个答案

于 2011-07-25T12:28:17.917 回答
2

我读了大约一年的大量文字解释,并没有掌握有关 r 值引用的所有内容,直到我观看了 Scott Meyer 的精彩演讲:http : //skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-和右值引用

他以一种有趣且缓慢的方式进行解释,以理解过程中发生的每一件事。

我知道,它是 1 小时 30 分,但实际上,这是我去年得到的最好的解释。

在阅读了这些文章(就像其他答案一样)之后,观看这个视频确实在我的脑海中以一致的方式将它融合在一起,几天后我能够向一些同事解释它并解释如何使用 std::unique_ptr (如它是相关的——它只允许移动语义,而不是复制),因为它需要理解 std::move(),这需要理解移动语义。

于 2011-07-25T23:34:54.370 回答
2

很高兴看到这样的问题,我很高兴分享我的观点。我认为您是在询问有关 C++ 语言本身名称的错误修复,而不仅仅是另一个 C++ 语言功能。这个“虫子”已经存在了几十年。也就是复制构造函数

如果你知道物理学中有很多东西是不能被复制的,比如能量和质量,那么复制构造函数似乎很奇怪。这只是个玩笑,但事实上在编程领域也是如此,像独占文件描述符这样的对象是不可复制的。所以 C++ 程序员和设计者发明了一些技巧来解决这个问题。有 3 个著名的:NRVOboost::noncopyablestd::auto_ptr.

NRVO(命名返回值优化)是一种让函数按值返回对象而不调用复制构造函数的技术。但是NRVO的问题在于,虽然实际上并没有调用public拷贝构造函数,但是仍然需要声明拷贝构造函数,也就是说,对象boost::noncopyable不兼容NRVO。

std::auto_ptr是绕过复制构造函数的另一个尝试。您可能已经看到它的“复制构造函数”实现如下

template <typename _T>
auto_ptr(auto_ptr<_T>& source)
{
     _ptr = source._ptr; // where _ptr is the pointer to the contained object
     source._ptr = NULL;
}

这根本不是副本,而是“移动”。您可以将这种行为视为移动语义的原型。

std::auto_ptr也有它自己的问题:它与 STL 容器不兼容。所以,不幸的是,任何关于不可复制的事情都是痛苦的。

这很痛苦,直到编译器制造商最终发布并实现了 C++0x 移动语义。

简单来说,您可以将移动语义视为与 的“复制”行为相同的东西std::auto_ptr,但语言功能完全支持它,因此它可以很好地与容器和算法一起使用。

顺便说一句,在 C++0x 中std::auto_ptr已弃用,建议使用新的模板类型std::unique_ptr

我的故事现在就要结束了。如果您想了解更多关于它的信息,例如奇怪的语法和右值系统,请参阅其他帖子。

于 2011-07-26T03:37:01.677 回答