赞赏良好的链接。
答案 0 :(得分:222)
Wikipedia Page on C++11 R-value references and move constructors
Type &&
),则使用移动构造函数而不是复制构造函数。std::move()
是一个转换器,它生成对象的右值引用,以便从中移动。这是一种避免复制的新C ++方法。例如,使用移动构造函数,std::vector
只能将其内部指针复制到新对象,使移动的对象处于不正确的状态,从而避免复制所有数据。这将是C ++ - 有效。
尝试谷歌搜索移动语义,右值,完美转发。
答案 1 :(得分:155)
虽然std::move()
在技术上是一个功能 - 我会说它不是真正一个功能。它是编译器考虑表达式值的方式之间的转换器。
首先要注意的是std::move()
实际上并没有移动任何东西。它将表达式从lvalue(例如命名变量)转换为xvalue。 xvalue告诉编译器:
你可以掠夺我,移动我持有的任何东西并在其他地方使用它(因为我很快就会被摧毁)"。
换句话说,当您使用std::move(x)
时,您允许编译器蚕食x
。因此,如果x
在内存中有自己的缓冲区 - 在std::move()
之后,编译器可以让另一个对象拥有它。
您也可以从prvalue移动(例如临时转移),但这很少有用。
提出这个问题的另一种方法是"我将为现有对象提供什么资源?"好吧,如果您正在编写应用程序代码,那么您可能不会对编译器创建的临时对象进行大量处理。因此,主要是在构造函数,运算符方法,类似标准库算法等函数的地方执行此操作,其中对象自动创建和销毁很多。当然,这只是一个经验法则。
典型的用途是“移动”。资源从一个对象到另一个对象而不是复制。 @Guillaume链接到this page,它有一个简单的简短示例:交换两个对象,复制较少。
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);
}
想想当T是大小为n的vector<int>
时会发生什么。在第一个版本中,您读取和写入3 * n个元素,在第二个版本中,您基本上只读取和写入向量的3个指针。缓冲区。当然,T级需要知道如何进行移动;你的类应该有一个移动赋值运算符和一个T类的移动构造函数,以便它可以工作。
答案 2 :(得分:132)
当您需要在其他地方“传输”对象的内容时,可以使用move,而无需复制(即内容不重复,这就是为什么它可以用于某些不可复制的对象,如unique_ptr )。使用std :: move,对象也可以在不进行复制的情况下获取临时对象的内容(并节省大量时间)。
此链接确实帮助了我:
http://thbecker.net/articles/rvalue_references/section_01.html
如果我的答案来得太晚,我很抱歉,但我也在寻找std :: move的良好链接,我发现上面的链接有点“严肃”。
这强调了r值参考,在哪种情况下你应该使用它们,我认为它更详细,这就是为什么我想在这里分享这个链接。
答案 3 :(得分:29)
std :: move本身并没有太大作用。我认为它调用了一个对象的移动构造函数,但它实际上只执行一个类型转换(将一个左值变量转换为一个右值,以便所述变量可以作为参数传递给一个移动构造函数或赋值运算符)。 p>
所以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。
答案 4 :(得分:5)
“这是什么?” 和“它是做什么的?” 。
我将举一个“何时使用它”的示例。
例如,我们有一个类,其中包含大量的资源,例如大数组。
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
与move constructor
使得转换资源变得容易。
std::move
还有什么用?
std::move
在对元素数组进行排序时也很有用。许多排序算法(例如选择排序和冒泡排序)通过交换元素对来工作。以前,我们不得不诉诸于复制语义来进行交换。现在,我们可以使用移动语义,这将更加有效。
如果我们想将一个智能指针管理的内容移动到另一个智能指针,也会很有用。
引用:
答案 5 :(得分:1)
<canvas id="myCanvas" width="770" height="428" style="border:1px solid #d3d3d3;">
</canvas>
本身不执行任何操作,而只是执行std::move
。根据{{3}}
这完全等同于static_cast到右值引用类型。
因此,这取决于您在static_cast
之后分配给变量的类型,如果类型具有move
或constructors
且带有右值参数,则该变量可能会或可能不会窃取原始变量的内容,因此,它可能会将原始变量保留在assign operators
中:
除非另有说明,否则所有已移离的标准库对象都不会置于有效但未指定的状态。
因为对于内置文字类型(例如整数和原始指针)没有特殊的unspecified state
或move constructor
,因此,这只是这些类型的简单副本。
答案 6 :(得分:0)
这是一个完整的示例,使用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;
}