关于c ++:move语义 – 它的全部意义何在?

Move semantics - what it's all about?

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

Possible Duplicate:
Can someone please explain move semantics to me?

有人能给我指出一个好的来源或者解释一下这里的移动语义是什么?


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

定义

移动语义定义特定操作的行为。大多数时候,它们与复制语义形成了对比,因此首先定义它们是很有用的。

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

1
2
3
4
// Copy semantics
assert(b == c);
a = b;
assert(a == b && b == c);

也就是说,a最终等于b,我们保持b不变。

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

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

注意,在使用移动语义的赋值之后,不再保证b保持不变。这是至关重要的区别。

使用

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

1
struct A { T* x; };

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

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

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

1
2
3
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对象。

我们可以用复制语义的正常方式来实现它。

1
2
3
4
5
6
void swap(A& a, A& b)
{
    A t = a;
    a = b;
    b = t;
}

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

  • 我们创建了一个at的拷贝。
  • 然后我们把b复制到a中。
  • 然后将t复制到b中。
  • 最后,摧毁t

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

在我们的例子中,很容易在a类型的对象周围移动:

1
2
3
4
5
6
// Not C++0x
void move(A& lhs, A& rhs)
{
    lhs.x = rhs.x;
    rhs.x = nullptr;
}

我们只需将rhs的指针移动到lhs中,然后放弃该指针的rhs所有权(通过将其设置为空)。这应该解释为什么较弱的后移语义条件允许优化。

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

1
2
3
4
5
6
7
void swap(A& a, A& b)
{
    A t;
    move(t, a);
    move(a, b);
    move(b, t);
}

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

C++0X

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

1
a = b;

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

1
a = std::move(b);

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

要定义移动优化,需要定义移动构造函数和移动分配运算符:

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

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

结论

基本上就是这样。注意,rValk引用也用于允许在C++ 0x中完全转发(由于特定的类型系统在R值引用和其他类型之间的交互),但这实际上与移动语义无关,所以我在这里没有讨论过。


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

看看这个答案。


很高兴看到这样一个问题,我很高兴分享我的观点。我想你是在询问一个关于C++语言本身的错误修复,而不仅仅是另一个C++语言的特性。"臭虫"已经存在了几十年了。也就是说,复制构造函数。

如果你知道在物理学中有很多东西是不能被复制的,比如能量和质量,复制构造器看起来很奇怪。这只是个玩笑,但事实上,在编程的世界里,像独占文件描述符这样的对象是不可复制的。所以C++程序员和设计师发明了一些技巧来解决这个问题。有三个著名的:NRVO,boost::noncopyablestd::auto_ptr

nrvo(命名为返回值优化)是一种技术,它允许函数按值返回对象,而不调用复制构造函数。但是nrvo的问题是,尽管实际上没有调用copy构造函数,但仍然需要一个publiccopy构造函数声明,这意味着boost::noncopyable的对象与nrvo不兼容。

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

1
2
3
4
5
6
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中,EDCOX1〔1〕被禁止,并且推荐一种新的模板类型EDCOX1×8。

我的故事现在就要结束了。如果你想更多地了解它,比如奇怪的语法和右值系统,请参考其他文章。


我读了大约一年的大量文本解释,直到看了Scott Meyer的精彩演讲:http://skillsmatter.com/podcast/home/move-semanticsPerfect-forwarding-and-r value-references,才了解R值参考的所有内容。

他以一种有趣和缓慢的方式解释过程中发生的每一件事。

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

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