有人能指出一个好的消息来源或在这里解释什么是移动语义?
答案 0 :(得分:34)
暂时忘掉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
的副本创建为t
。b
复制到a
。t
复制到b
。t
。如果复制T
个对象很昂贵,那么这很浪费。如果我要求您在计算机上交换两个文件,则不会创建第三个文件,然后在销毁临时文件之前复制并粘贴文件内容,是吗?不,你移动一个文件,移动第二个到第一个位置,然后移动第一个文件回到第二个文件。无需复制数据。
在我们的例子中,可以轻松移动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允许通过其右值引用功能移动语义。具体来说,那种操作:
a = b;
当b
是右值引用(拼写为T&&
)时有移动语义,否则它们具有复制语义。当std::move
不是右值引用时,您可以使用move
函数(与我前面定义的b
不同)强制移动语义:
a = std::move(b);
std::move
是一个简单的函数,它基本上将其参数转换为右值引用。请注意,表达式的结果(例如函数调用)是自动rvalue引用,因此您可以在不更改代码的情况下利用移动语义。
要定义移动优化,您需要定义移动构造函数并移动赋值运算符:
T::T(T&&);
T& operator=(T&&);
由于这些操作具有移动语义,因此您可以自由修改传入的参数(假设您将对象置于可破坏状态)。
基本上就是这一切。请注意,右值引用也用于允许在C ++ 0x中完美转发(由于rvalue引用和其他类型之间特别精心设计的类型系统交互),但这与移动语义无关,所以我没有讨论过它在这里。
答案 1 :(得分:4)
基本上,右值引用允许您检测对象何时是临时对象而您不必保留其内部状态。这允许更高效的代码,其中C ++ 03曾经必须一直复制,在C ++ 0x中,您可以继续重用相同的资源。此外,右值参考可实现完美转发。
查看this answer。
答案 2 :(得分:2)
我阅读了大约一年的大量文字解释并且没有掌握关于r值参考的所有内容,直到我看到Scott Meyer的这篇精彩演讲:http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references
他以一种有趣且缓慢的方式解释,以理解过程中发生的每件事。
我知道,1小时30分,但实际上,这是我去年的最佳解释。
在阅读了这些文章之后(就像其他答案一样),观看这段视频确实以一致的方式将它融合在一起,几天后我能够向一些同事解释并解释如何使用std :: unique_ptr(因为它是相关的 - 它只允许移动语义,而不是复制),因为它需要理解std :: move(),这需要理解移动语义。
答案 3 :(得分:2)
boost::noncopyable
和std::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容器不兼容。所以,不幸的是,关于noncopyable的任何事都很痛苦。
这很痛苦,直到C ++ 0x移动语义最终由编译器制造商发布和实现。
以简单的方式,您可以将移动语义看作与std::auto_ptr
的“复制”行为相同,但语言功能完全支持,因此它适用于容器和算法。
顺便说一下,在C ++ 0x中,不推荐使用std::auto_ptr
,建议使用新的模板类型std::unique_ptr
。
我的故事现在结束了。如果您想了解更多关于它的信息,请参阅其他帖子,如奇怪的语法和右值系统。