移动语义 - 它的全部意义何在?

sma*_*llB 10 c++ rvalue-reference move-semantics c++11

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

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

Pet*_*der 34

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

定义

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

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

// Copy semantics
assert(b == c);
a = b;
assert(a == b && b == c);
Run Code Online (Sandbox Code Playgroud)

a结束等于b,我们b保持不变.

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

// Move semantics
assert(b == c);
move(a, b); // not C++0x
assert(a == c);
Run Code Online (Sandbox Code Playgroud)

请注意,在b使用移动语义分配后,不再保证保持不变.这是至关重要的区别.

用途

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

struct A { T* x; };
Run Code Online (Sandbox Code Playgroud)

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

bool operator==(const A& lhs, const A& rhs) { return *lhs.x == *rhs.x; }
Run Code Online (Sandbox Code Playgroud)

最后假设我们定义一个对象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; }
Run Code Online (Sandbox Code Playgroud)

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

我们可以通过复制语义以正常方式完成它.

void swap(A& a, A& b)
{
    A t = a;
    a = b;
    b = t;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我们创建了一个ainto 的副本t.
  • 然后我们复制ba.
  • 然后复制tb.
  • 最后,毁灭t.

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

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

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

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

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

void swap(A& a, A& b)
{
    A t;
    move(t, a);
    move(a, b);
    move(b, t);
}
Run Code Online (Sandbox Code Playgroud)

移动语义的另一个优点是它允许您移动无法复制的对象.一个很好的例子是std::auto_ptr.

的C++ 0x

C++ 0x允许通过其右值引用功能移动语义.具体来说,这种操作:

a = b;
Run Code Online (Sandbox Code Playgroud)

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

a = std::move(b);
Run Code Online (Sandbox Code Playgroud)

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

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

T::T(T&&);
T& operator=(T&&);
Run Code Online (Sandbox Code Playgroud)

由于这些操作具有移动语义,因此您可以自由修改传入的参数(假设您将对象置于可破坏状态).

结论

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