跳转至

C++ std::move 原理与用法

std::move 是一个非常重要的函数,它提供了一种方式,可以将一个左值对象标记为一个特殊类型的右值对象,即 xvalue。这种转变是为了允许执行移动语义,而不是执行常规的拷贝语义。

  • 为什么要使用 std::move
  • 实际应用场景

1.为什么要使用 std::move

通常情况下,只有右值对象才能进行移动操作,因为移动构造函数使用右值引用作为参数,而右值引用只能绑定到右值。因此,如果你想从一个由左值表达式表示的对象中执行移动操作,你需要使用 std::move 来将其转换为一个 xvalue(特殊的右值)。

使用 std::move 时需要谨慎,因为移动操作会将对象置于一个通常只允许销毁的状态。因此,最好仅对对象的最后一次使用进行移动操作。

std::move 并不实际执行移动,需要明确的是,std::move 并不会实际执行对象的移动操作。它只是将给定的左值对象转换为右值引用。下面是 std::move 的一个可能实现:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_cast<typename remove_reference<T>::type&&>(t);
}

2.实际使用场景

2.1 移动构造而非拷贝构造

在函数 f1 中,参数 a 是一个右值引用的左值,而不是右值本身,因为它有一个名称。因此,为了强制执行 Foo 的移动构造,我们必须将其转换为一个右值,而 std::move 恰好可以完美地做到这一点。

struct Foo { 
    ... 
    Foo(const Foo&) {} 
    Foo(Foo&&) {} 
}; 

void f1(Foo&& a) { 
    Foo b(a); // 调用拷贝构造函数,而非移动构造函数
} 

void f2(Foo&& a) { 
    Foo b(std::move(a)); // right
} 

2.2 所有权管理

假设有如下代码:

Foo* p = new Foo();
data.push_back(p);

上述代码可以正常工作,但你无法确定指针的所有权归属。谁来删除指针?或者指针应该在何时被删除?它会在容器被销毁时被删除吗?

在 C++11 中,std::unique_ptr<Foo> p(new Foo()); 这条语句会产生编译错误。原因在于唯一所有权规则被应用。

但是,你可以使用 std::move 来转移指针的所有权:

data.push_back(std::move(p));

现在,指针的所有权被转移到了容器中,在容器生命周期结束时将被删除。虽然对象 p 仍然存在,但任何试图解引用它的尝试将生成空指针异常。

事实上,在移动操作之后,p 拥有的内部指针已经被设置为 nullptr

综上所述,std::move 的作用在于提供了一种手段,能够安全地实现对象的移动语义,转变其左值属性为右值,使其可以被移动构造函数或移动赋值操作符所调用。正确地使用 std::move 可以提高性能并确保正确的所有权转移。

那么下一个问题,什么时候不需要使用std::move呢?

评论