C++ 什么是 std::move(),什么时候应该使用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3413470/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is std::move(), and when should it be used?
提问by Basilevs
- What is it?
- What does it do?
- When should it be used?
- 它是什么?
- 它有什么作用?
- 应该什么时候使用?
Good links are appreciated.
良好的链接表示赞赏。
采纳答案by Scharron
Wikipedia Page on C++11 R-value references and move constructors
- In C++11, in addition to copy constructors, objects can have move constructors.
(And in addition to copy assignment operators, they have move assignment operators.) - The move constructor is used instead of the copy constructor, if the object has type "rvalue-reference" (
Type &&
). std::move()
is a cast that produces an rvalue-reference to an object, to enable moving from it.
- 在 C++11 中,除了复制构造函数之外,对象还可以有移动构造函数。
(除了复制赋值运算符,它们还有移动赋值运算符。) - 如果对象具有“右值引用”类型 (
Type &&
) ,则使用移动构造函数而不是复制构造函数。 std::move()
是一个转换,它产生一个对对象的右值引用,以允许从它移动。
It's a new C++ way to avoid copies. For example, using a move constructor, a std::vector
could just copy its internal pointer to data to the new object, leaving the moved object in an moved from state, therefore not copying all the data. This would be C++-valid.
这是一种避免复制的新 C++ 方法。例如,使用移动构造函数,astd::vector
可以将其内部指向数据的指针复制到新对象,使移动的对象处于移动状态,因此不会复制所有数据。这将是 C++-valid。
Try googling for move semantics, rvalue, perfect forwarding.
尝试使用谷歌搜索移动语义、右值、完美转发。
回答by einpoklum
1. "What is it?"
1、“什么事?”
While std::move()
is technically a function - I would say it isn't reallya function. It's sort of a converterbetween ways the compiler considers an expression's value.
虽然std::move()
从技术上讲是一个函数 - 我会说它不是真正的函数。它是编译器考虑表达式值的方式之间的转换器。
2. "What does it do?"
2.“它有什么作用?”
The first thing to note is that std::move()
doesn't actually move anything. It converts an expression from being an lvalue(such as a named variable) to being an xvalue. An xvalue tells the compiler:
首先要注意的是,std::move()
实际上并没有移动任何东西。它将表达式从左值(例如命名变量)转换为xvalue。xvalue 告诉编译器:
You can plunder me, moveanything I'm holding and use it elsewhere (since I'm going to be destroyed soon anyway)".
你可以掠夺我,移动我持有的任何东西并在其他地方使用它(因为反正我很快就会被摧毁)”。
in other words, when you use std::move(x)
, you're allowing the compiler to cannibalize x
. Thus if x
has, say, its own buffer in memory - after std::move()
ing the compiler can have another object own it instead.
换句话说,当您使用 时std::move(x)
,您允许编译器蚕食x
. 因此,如果x
在内存中有自己的缓冲区 - 在std::move()
ing之后,编译器可以让另一个对象拥有它。
You can also move from a prvalue(such as a temporary you're passing around), but this is rarely useful.
您也可以从纯右值(例如您正在传递的临时值)移动,但这很少有用。
3. "When should it be used?"
3、“什么时候用?”
Another way to ask this question is "What would I cannibalize an existing object's resources for?" well, if you're writing application code, you would probably not be messing around a lot with temporary objects created by the compiler. So mainly you would do this in places like constructors, operator methods, standard-library-algorithm-like functions etc. where objects get created and destroyed automagically a lot. Of course, that's just a rule of thumb.
问这个问题的另一种方式是“我会为了什么而蚕食现有对象的资源?” 好吧,如果您正在编写应用程序代码,您可能不会对编译器创建的临时对象进行大量处理。所以主要你会在构造函数、操作符方法、标准库算法类函数等地方这样做,在这些地方,对象会被自动创建和销毁。当然,这只是一个经验法则。
A typical use is 'moving' resources from one object to another instead of copying. @Guillaume links to this pagewhich has a straightforward short example: swapping two objects with less copying.
一个典型的用途是将资源从一个对象“移动”到另一个对象而不是复制。@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)
}
using move allows you to swap the resources instead of copying them around:
使用 move 允许您交换资源而不是复制它们:
template <class T>
swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
Think of what happens when T
is, say, vector<int>
of size n. In the first version you read and write 3*n elements, in the second version you basically read and write just the 3 pointers to the vectors' buffers, plus the 3 buffers' sizes. Of course, class T
needs to know how to do the moving; your class should have a move-assignment operator and a move-constructor for class T
for this to work.
想想会发生什么情况T
是,比如说,vector<int>
大小为n。在第一个版本中,您读写 3*n 个元素,在第二个版本中,您基本上只读写指向向量缓冲区的 3 个指针,以及 3 个缓冲区的大小。当然,类T
需要知道如何移动;你的班级应该有一个移动赋值运算符和一个移动构造函数来T
让这个班级工作。
回答by Guillaume
You can use move when you need to "transfer" the content of an object somewhere else, without doing a copy (i.e. the content is not duplicated, that's why it could be used on some non-copyable objects, like a unique_ptr). It's also possible for an object to take the content of a temporary object without doing a copy (and save a lot of time), with std::move.
当您需要将对象的内容“转移”到其他地方时,您可以使用 move ,而无需进行复制(即内容不重复,这就是为什么它可以用于某些不可复制的对象,例如 unique_ptr)。对象也可以使用 std::move 获取临时对象的内容而不进行复制(并节省大量时间)。
This link really helped me out :
这个链接真的帮助了我:
http://thbecker.net/articles/rvalue_references/section_01.html
http://thbecker.net/articles/rvalue_references/section_01.html
I'm sorry if my answer is coming too late, but I was also looking for a good link for the std::move, and I found the links above a little bit "austere".
如果我的回答来得太晚,我很抱歉,但我也在寻找 std::move 的好链接,我发现上面的链接有点“严肃”。
This puts the emphasis on r-value reference, in which context you should use them, and I think it's more detailed, that's why I wanted to share this link here.
这将重点放在 r 值引用上,你应该在什么上下文中使用它们,我认为它更详细,这就是我想在这里分享这个链接的原因。
回答by Christopher Oezbek
Q: What is std::move
?
问:什么是std::move
?
A: std::move()
is a function from the C++ Standard Library for casting to a rvalue reference.
A:std::move()
是 C++ 标准库中的一个函数,用于转换为右值引用。
Simplisticly std::move(t)
is equivalent to:
简单std::move(t)
来说相当于:
static_cast<T&&>(t);
An rvalue is a temporary that does not persist beyond the expression that defines it, such as a intermediate function result which is never stored in a variable.
右值是一个临时的,不会在定义它的表达式之外持续存在,例如中间函数结果,它永远不会存储在变量中。
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
An implementation for std::move() is given in N2027: "A Brief Introduction to Rvalue References"as follows:
N2027: "A Brief Introduction to Rvalue References" 中给出了 std::move() 的实现,如下所示:
template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
return a;
}
As you can see, std::move
returns T&&
no matter if called with a value (T
), reference type (T&
) or rvalue reference (T&&
).
如您所见,无论是使用值 ( )、引用类型 ( ) 还是右值引用 ( )调用,都会std::move
返回。T&&
T
T&
T&&
Q: What does it do?
问:它有什么作用?
A: As a cast, it does not do anything during runtime. It is only relevant at compile time to tell the compiler that you would like to continue considering the reference as an rvalue.
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)`
What it does notdo:
它能做什么不能做的:
- Make a copy of the argument
- Call the copy constructor
- Change the argument object
- 复制论证
- 调用复制构造函数
- 更改参数对象
Q: When should it be used?
问:应该什么时候使用?
A: You should use std::move
if you want to call functions which support move semantics with an argument which is not an rvalue (temporary expression).
答:std::move
如果您想使用非右值(临时表达式)的参数调用支持移动语义的函数,则应使用。
This begs the following follow-up questions for me:
这为我带来了以下后续问题:
What are move semantics? Move semantics in contrast to copy semantics is a programming technique in which the members of a object are initialized by 'taking over' instead of copying another object's members. Such 'take over' makes only sense with pointers and resource handles, which can be cheaply transferred by copying the pointer or integer handle rather than the underlying data.
What kind of classes and objects support move semantics? It is up to you as a developer to implement move semantics in your own classes if these would benefit from transferring their members instead of copying them. Once you implement move semantics, you will directly benefit from work from many library programmers who have added support for handling classes with move semantics efficiently.
Why can't the compiler figure it out on its own? The compiler cannot just call another overload of a function unless you say so. You must help the compiler choose whether the regular or move version of function should be called.
In which situations would I want to tell the compiler that it should treat a variable as an rvalue? This will most likely happen in template or library functions, where you know that an intermediate result could be salvaged.
什么是移动语义?与复制语义相反,移动语义是一种编程技术,其中通过“接管”而不是复制另一个对象的成员来初始化对象的成员。这种“接管”只对指针和资源句柄有意义,它们可以通过复制指针或整数句柄而不是底层数据来廉价地传输。
什么样的类和对象支持移动语义?作为开发人员,您有责任在您自己的类中实现移动语义,如果这些可以从转移成员而不是复制成员中受益。一旦您实现了移动语义,您将直接受益于许多库程序员的工作,他们添加了对有效处理具有移动语义的类的支持。
为什么编译器不能自己解决?除非你这么说,否则编译器不能只调用函数的另一个重载。您必须帮助编译器选择应该调用函数的常规版本还是移动版本。
在哪些情况下我想告诉编译器它应该将变量视为右值?这很可能发生在模板或库函数中,您知道可以挽救中间结果。
回答by user929404
std::move itself doesn't really do much. I thought that it called the moved constructor for an object, but it really just performs a type cast (casting an lvalue variable to an rvalue so that the said variable can be passed as an argument to a move constructor or assignment operator).
std::move 本身并没有做太多事情。我认为它为对象调用了移动的构造函数,但它实际上只是执行类型转换(将左值变量转换为右值,以便所述变量可以作为参数传递给移动构造函数或赋值运算符)。
So std::move is just used as a precursor to using move semantics. Move semantics is essentially an efficient way for dealing with temporary objects.
所以 std::move 只是用作使用移动语义的前身。移动语义本质上是一种处理临时对象的有效方式。
Consider Object A = B + C + D + E + F;
考虑对象 A = B + C + D + E + F;
This is nice looking code, but E + F produces a temporary object. Then D + temp produces another temporary object and so on. In each normal "+" operator of a class, deep copies occur.
这是看起来不错的代码,但 E + F 会生成一个临时对象。然后 D + temp 产生另一个临时对象,依此类推。在一个类的每个正常的“+”运算符中,都会发生深拷贝。
For example
例如
Object Object::operator+ (const Object& rhs) {
Object temp (*this);
// logic for adding
return temp;
}
The creation of the temporary object in this function is useless - these temporary objects will be deleted at the end of the line anyway as they go out of scope.
在这个函数中创建临时对象是没有用的——这些临时对象在超出范围时无论如何都会在行尾被删除。
We can rather use move semantics to "plunder" the temporary objects and do something like
我们宁愿使用移动语义来“掠夺”临时对象并执行类似的操作
Object& Object::operator+ (Object&& rhs) {
// logic to modify rhs directly
return rhs;
}
This avoid needless deep copies being made. With reference to the example, the only part where deep copying occurs is now E + F. The rest uses move semantics. The move constructor or assignment operator also needs to be implemented to assign the result to A.
这避免了进行不必要的深拷贝。参考示例,现在唯一发生深度复制的部分是 E + F。其余部分使用移动语义。还需要实现移动构造函数或赋值运算符来将结果赋值给 A。
回答by Jayhello
"What is it?"and "What does it do?"has been explained above.
“它是什么?” 和 “它有什么作用?” 上面已经解释过了。
I will give a example of "when it should be used".
我将举一个“什么时候应该使用它”的例子。
For example, we have a class with lots of resource like big array in it.
例如,我们有一个类,里面有很多资源,比如大数组。
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
};
Test code:
测试代码:
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;
}
output as below:
输出如下:
default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid
We can see that std::move
with move constructor
makes transform resource easily.
我们可以看到,std::move
withmove constructor
使转换资源变得容易。
Where else is std::move
useful?
还有哪里std::move
有用?
std::move
can also be useful when sorting an array of elements. Many sorting algorithms (such as selection sort and bubble sort) work by swapping pairs of elements. Previously, we've had to resort to copy-semantics to do the swapping. Now we can use move semantics, which is more efficient.
std::move
在对元素数组进行排序时也很有用。许多排序算法(例如选择排序和冒泡排序)通过交换元素对来工作。以前,我们不得不求助于复制语义来进行交换。现在我们可以使用更有效的移动语义。
It can also be useful if we want to move the contents managed by one smart pointer to another.
如果我们想将一个智能指针管理的内容移动到另一个智能指针,它也很有用。
Cited:
引用:
回答by Neil McGill
Here is a full example, using std::move for a (simple) custom vector
这是一个完整的示例,将 std::move 用于(简单的)自定义向量
Expected output:
预期输出:
c: [10][11]
copy ctor called
copy of c: [10][11]
move ctor called
moved c: [10][11]
Compile as:
编译为:
g++ -std=c++2a -O2 -Wall -pedantic foo.cpp
Code:
代码:
#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;
}