- 它是什么?
- 它有什么作用?
- 什么时候应该使用它?
好的链接表示赞赏。
虽然std::move()
在技术上是一个功能 - 我会说它不是一个真正的功能。它是编译器考虑表达式值的方式之间的一种转换器。
首先要注意的是,std::move()
它实际上并没有移动任何东西。它将表达式从左值(例如命名变量)更改为xvalue。一个 xvalue 告诉编译器:
你可以掠夺我,移动我持有的任何东西并在其他地方使用(因为无论如何我很快就会被摧毁)”。
换句话说,当您使用 时std::move(x)
,您允许编译器蚕食x
. 因此x
,如果在内存中拥有自己的缓冲区 - 在std::move()
编译器可以让另一个对象拥有它之后。
您也可以从纯右值(例如您正在传递的临时值)中移动,但这很少有用。
提出这个问题的另一种方法是“我会为了什么而蚕食现有对象的资源?” 好吧,如果您正在编写应用程序代码,那么您可能不会对编译器创建的临时对象进行过多处理。所以主要你会在构造函数、操作方法、标准库算法等函数等地方执行此操作。在这些地方,对象会自动创建和销毁很多。当然,这只是一个经验法则。
一个典型的用途是将资源从一个对象“移动”到另一个对象,而不是复制。@Guillaume 链接到这个页面,其中有一个简单的简短示例:用更少的复制交换两个对象。
template <class T>
swap(T& a, T& b) {
T tmp(a); // we now have two copies of a
a = b; // we now have two copies of b (+ discarded a copy of a)
b = tmp; // we now have two copies of tmp (+ discarded a copy of b)
}
使用 move 允许您交换资源而不是复制它们:
template <class T>
swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
想想当大小为 n 时会发生T
什么vector<int>
。在第一个版本中,您读取和写入 3*n 个元素,在第二个版本中,您基本上只读取和写入指向向量缓冲区的 3 个指针,以及 3 个缓冲区的大小。当然,班级T
需要知道如何搬家;你的班级应该有一个移动赋值运算符和一个移动构造函数T
,这样才能工作。
Type &&
如果对象的类型为“右值引用”( ),则使用移动构造函数而不是复制构造函数。std::move()
是一个产生对对象的右值引用的强制转换,以便能够从它移动。这是一种避免复制的新 C++ 方法。例如,使用移动构造函数,astd::vector
可以将其指向数据的内部指针复制到新对象,使移动的对象处于已移动的状态,因此不会复制所有数据。这将是 C++ 有效的。
尝试在谷歌上搜索移动语义、右值、完美转发。
当您需要将对象的内容“转移”到其他地方时,您可以使用 move,而无需进行复制(即内容不重复,这就是为什么它可以用于某些不可复制的对象,例如 unique_ptr)。对象也可以使用 std::move 获取临时对象的内容而不进行复制(并节省大量时间)。
这个链接真的帮助了我:
http://thbecker.net/articles/rvalue_references/section_01.html
如果我的回答来得太晚了,我很抱歉,但我也在为 std::move 寻找一个好的链接,我发现上面的链接有点“严肃”。
这强调了 r-value 引用,在什么情况下你应该使用它们,我认为它更详细,这就是我想在这里分享这个链接的原因。
std::move
?A:std::move()
是 C++ 标准库中的一个函数,用于转换为右值引用。
简单std::move(t)
地等价于:
static_cast<T&&>(t);
右值是一个临时值,它不会在定义它的表达式之外持续存在,例如从不存储在变量中的中间函数结果。
int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated
N2027中给出了 std::move() 的实现:“右值引用简介” ,如下所示:
template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
return a;
}
如您所见,无论是使用值 ( )、引用类型 ( ) 还是右值引用 ( ) 调用,std::move
都会返回。T&&
T
T&
T&&
A:作为一个演员,它在运行时不做任何事情。仅在编译时告诉编译器您希望继续将引用视为右值是相关的。
foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)
int a = 3 * 5;
foo(a); // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`
它不做什么:
答:std::move
如果您想使用不是右值(临时表达式)的参数调用支持移动语义的函数,则应该使用。
这为我引出了以下后续问题:
什么是移动语义?与复制语义相比,移动语义是一种编程技术,其中对象的成员通过“接管”而不是复制另一个对象的成员来初始化。这种“接管”只对指针和资源句柄有意义,可以通过复制指针或整数句柄而不是底层数据来廉价地传输它们。
什么样的类和对象支持移动语义?作为开发人员,您可以在自己的类中实现移动语义,如果这些可以从转移其成员而不是复制它们中受益。一旦您实现了移动语义,您将直接受益于许多库程序员的工作,他们添加了对有效处理具有移动语义的类的支持。
为什么编译器不能自己解决?除非您这么说,否则编译器不能只调用函数的另一个重载。您必须帮助编译器选择应该调用函数的常规版本还是移动版本。
在哪些情况下我想告诉编译器它应该将变量视为右值?这很可能发生在模板或库函数中,您知道可以挽救中间结果(而不是分配新实例)。
std::move 本身并没有真正做太多。我认为它调用了对象的移动构造函数,但它实际上只是执行类型转换(将左值变量转换为右值,以便所述变量可以作为参数传递给移动构造函数或赋值运算符)。
所以 std::move 只是用作使用移动语义的先驱。移动语义本质上是一种处理临时对象的有效方法。
考虑对象A = B + (C + (D + (E + F)));
这是看起来不错的代码,但是 E + F 会生成一个临时对象。然后 D + temp 产生另一个临时对象,依此类推。在类的每个普通“+”运算符中,都会发生深拷贝。
例如
Object Object::operator+ (const Object& rhs) {
Object temp (*this);
// logic for adding
return temp;
}
在这个函数中创建临时对象是没有用的——这些临时对象将在行尾被删除,因为它们超出了范围。
我们宁愿使用移动语义来“掠夺”临时对象并执行类似的操作
Object& Object::operator+ (Object&& rhs) {
// logic to modify rhs directly
return rhs;
}
这避免了不必要的深拷贝。参考示例,现在唯一发生深度复制的部分是 E + F。其余部分使用移动语义。还需要实现移动构造函数或赋值运算符以将结果分配给 A。
“它是什么?” “它有 什么作用?” 上面已经解释过了。
我将举一个“何时应该使用”的例子。
例如,我们有一个类,里面有很多资源,比如大数组。
class ResHeavy{ // ResHeavy means heavy resource
public:
ResHeavy(int len=10):_upInt(new int[len]),_len(len){
cout<<"default ctor"<<endl;
}
ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
cout<<"copy ctor"<<endl;
}
ResHeavy& operator=(const ResHeavy& rhs){
_upInt.reset(new int[rhs._len]);
_len = rhs._len;
cout<<"operator= ctor"<<endl;
}
ResHeavy(ResHeavy&& rhs){
_upInt = std::move(rhs._upInt);
_len = rhs._len;
rhs._len = 0;
cout<<"move ctor"<<endl;
}
// check array valid
bool is_up_valid(){
return _upInt != nullptr;
}
private:
std::unique_ptr<int[]> _upInt; // heavy array resource
int _len; // length of int array
};
测试代码:
void test_std_move2(){
ResHeavy rh; // only one int[]
// operator rh
// after some operator of rh, it becomes no-use
// transform it to other object
ResHeavy rh2 = std::move(rh); // rh becomes invalid
// show rh, rh2 it valid
if(rh.is_up_valid())
cout<<"rh valid"<<endl;
else
cout<<"rh invalid"<<endl;
if(rh2.is_up_valid())
cout<<"rh2 valid"<<endl;
else
cout<<"rh2 invalid"<<endl;
// new ResHeavy object, created by copy ctor
ResHeavy rh3(rh2); // two copy of int[]
if(rh3.is_up_valid())
cout<<"rh3 valid"<<endl;
else
cout<<"rh3 invalid"<<endl;
}
输出如下:
default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid
我们可以看到std::move
withmove constructor
可以轻松地转换资源。
还有什么地方std::move
有用?
std::move
在对元素数组进行排序时也很有用。许多排序算法(例如选择排序和冒泡排序)通过交换元素对来工作。以前,我们不得不求助于复制语义来进行交换。现在我们可以使用更高效的移动语义。
如果我们想将一个智能指针管理的内容移动到另一个智能指针,它也很有用。
引用:
std::move
它本身什么也不做,而不是 a static_cast
。根据cppreference.com
它完全等同于将 static_cast 转换为右值引用类型。
因此,它取决于您在 之后分配给的变量的move
类型,如果该类型具有constructors
或assign operators
接受右值参数,它可能会或可能不会窃取原始变量的内容,因此,它可能会将原始变量保留为在一个unspecified state
:
除非另有说明,否则所有已被移动的标准库对象都处于有效但未指定的状态。
因为没有特殊的move constructor
或move assign operator
用于内置的字面量类型,例如整数和原始指针,所以,它将只是这些类型的简单副本。
这是一个完整的示例,将 std::move 用于(简单)自定义向量
预期输出:
c: [10][11]
copy ctor called
copy of c: [10][11]
move ctor called
moved c: [10][11]
编译为:
g++ -std=c++2a -O2 -Wall -pedantic foo.cpp
代码:
#include <iostream>
#include <algorithm>
template<class T> class MyVector {
private:
T *data;
size_t maxlen;
size_t currlen;
public:
MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }
MyVector<T> (const MyVector& o) {
std::cout << "copy ctor called" << std::endl;
data = new T [o.maxlen];
maxlen = o.maxlen;
currlen = o.currlen;
std::copy(o.data, o.data + o.maxlen, data);
}
MyVector<T> (const MyVector<T>&& o) {
std::cout << "move ctor called" << std::endl;
data = o.data;
maxlen = o.maxlen;
currlen = o.currlen;
}
void push_back (const T& i) {
if (currlen >= maxlen) {
maxlen *= 2;
auto newdata = new T [maxlen];
std::copy(data, data + currlen, newdata);
if (data) {
delete[] data;
}
data = newdata;
}
data[currlen++] = i;
}
friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
auto s = o.data;
auto e = o.data + o.currlen;;
while (s < e) {
os << "[" << *s << "]";
s++;
}
return os;
}
};
int main() {
auto c = new MyVector<int>(1);
c->push_back(10);
c->push_back(11);
std::cout << "c: " << *c << std::endl;
auto d = *c;
std::cout << "copy of c: " << d << std::endl;
auto e = std::move(*c);
delete c;
std::cout << "moved c: " << e << std::endl;
}