什么是std::移动(),什么时候应该使用它?

  1. 这是什么?
  2. 它有什么用?
  3. 什么时候应该使用?

良好的链接受到赞赏。

461170 次浏览

维基百科页面C++11个R值引用和移动构造函数

  1. 在C++11中,除了复制构造函数之外,对象还可以有移动构造函数。(除了复制赋值操作符之外,它们还有移动赋值操作符。
  2. 如果对象的类型为“右值-引用”(Type &&),则使用移动构造函数代替复制构造函数。
  3. std::move()是一个转换,它产生对对象的右值引用,以启用从它移动。

这是一种避免复制的新C++方法。例如,使用移动构造函数,std::vector可以将其指向数据的内部指针复制到新对象,使移动的对象处于从移动状态,因此不会复制所有数据。这将是C++有效的。

尝试谷歌搜索移动语义学,右值,完美转发。

当您需要将对象的内容“转移”到其他地方而无需复制时(即内容不重复,这就是为什么它可以用于一些不可复制的对象,例如unique_ptr)。对象也可以在不复制的情况下获取临时对象的内容(并节省大量时间),使用std::移动。

这个链接真的帮了我:

http://thbecker.net/articles/rvalue_references/section_01.html

我很抱歉,如果我的答案来得太晚了,但我也在寻找一个很好的链接,d::移动,我发现上面的链接有点“严峻”。

这强调了r值引用,在这种情况下你应该使用它们,我认为它更详细,这就是为什么我想在这里分享这个链接。

d::移动本身并没有真正做什么,我以为它调用了对象的移动构造函数,但它实际上只是执行类型转换(将左值变量转换为右值,以便所述变量可以作为参数传递给移动构造函数或赋值操作符)。

所以d::移动只是作为移动语义学的前奏。移动语义学本质上是处理临时对象的有效方法。

考虑对象A = B + (C + (D + (E + F)));

这是看起来不错的代码,但是E+F产生一个临时对象。然后D+temp产生另一个临时对象,依此类推。在类的每个普通“+”运算符中,都会发生深度副本。

例如

Object Object::operator+ (const Object& rhs) {Object temp (*this);// logic for addingreturn temp;}

在这个函数中创建临时对象是无用的——这些临时对象将在行尾被删除,因为它们超出了作用域。

我们可以使用移动语义学来“掠夺”临时对象并做类似的事情

 Object& Object::operator+ (Object&& rhs) {// logic to modify rhs directlyreturn rhs;}

这避免了不必要的深度复制。参考示例,现在唯一发生深度复制的部分是E+F。其余部分使用移动语义学。还需要实现移动构造函数或赋值操作符来将结果分配给A。

1.“它是什么?”

虽然std::move()在技术上是一个函数-我会说它不是真正的函数。它在编译器考虑表达式值的方式之间有点像转换器

2.“它做什么?”

首先要注意的是std::move()不会移动任何东西。它将表达式从左值(例如命名变量)更改为xvalue。xvalue告诉编译器:

你可以掠夺我,移动我持有的任何东西,并在其他地方使用它(因为我很快就会被摧毁)。

换句话说,当你使用std::move(x)时,你允许编译器蚕食x。因此,如果x在内存中有自己的缓冲区-在std::move()ing之后,编译器可以让另一个对象拥有它。

您也可以从prvalue(如临时传递),但这是很少有用的。

3.“应该在什么时候使用?”

问这个问题的另一种方式是“我要拆一个现有对象的资源做什么?”嗯,如果你正在编写应用程序代码,你可能不会经常处理编译器创建的临时对象。所以主要是在构造函数、运算符方法、类似标准库算法的函数等地方这样做,在这些地方对象会自动创建和销毁很多。当然,这只是一个经验法则。

一个典型的用法是将资源从一个对象“移动”到另一个对象,而不是复制。@Guillaume链接到此页面,它有一个简单的简短示例:交换两个复制较少的对象。

template <class T>swap(T& a, T& b) {T tmp(a);   // we now have two copies of aa = 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)}

使用移动允许您交换资源,而不是到处复制它们:

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个指针,加上3个缓冲区的大小。当然,类T需要知道如何移动;你的类应该有一个移动赋值操作符和一个移动构造函数用于类T才能工作。

问:什么是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 evaluatedint b = a; // a is a lvalue, keeps existing after expression is evaluated

N2027:《右值引用简介》中给出了d::的实现,如下所示:

template <class T>typename remove_reference<T>::type&&std::move(T&& a){return a;}

如您所见,无论使用值(T)、引用类型(T&)还是右值引用(T&&)调用,std::move都返回T&&

问:它是做什么的?

答:作为强制转换,它在运行时不做任何事情。只有在编译时告诉编译器您想继续将引用视为右值才相关。

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

这对我提出了以下后续问题:

  • 什么是移动语义学?与复制语义学相比,移动语义学是一种编程技术,其中对象的成员通过“接管”而不是复制另一个对象的成员来初始化。这种“接管”仅对指针和资源句柄有意义,可以通过复制指针或整数句柄而不是底层数据来廉价传输。

  • 什么样的类和对象支持移动语义学?作为开发人员,你可以在自己的类中实现移动语义学,如果这些类从传输它们的成员而不是复制它们中受益。一旦你实现了移动语义学,你将直接受益于许多库程序员的工作,他们增加了对高效处理移动语义学类的支持。

  • 为什么编译器不能自己解决?编译器不能只是调用函数的另一个重载,除非你这么说。你必须帮助编译器选择应该调用函数的常规版本还是移动版本。

  • 在哪些情况下,我想告诉编译器它应该将变量视为右值?这很可能发生在模板或库函数中,您知道可以挽救中间结果(而不是分配新实例)。

“是什么?”“它是做什么的?”已经在上面解释过了。

我举个例子“什么时候应该使用”。

例如,我们有一个类,其中包含大量资源,例如大数组。

class ResHeavy{ //  ResHeavy means heavy resourcepublic: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 validbool is_up_valid(){return _upInt != nullptr;}
private:std::unique_ptr<int[]> _upInt; // heavy array resourceint _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 objectResHeavy rh2 = std::move(rh); // rh becomes invalid
// show rh, rh2 it validif(rh.is_up_valid())cout<<"rh valid"<<endl;elsecout<<"rh invalid"<<endl;
if(rh2.is_up_valid())cout<<"rh2 valid"<<endl;elsecout<<"rh2 invalid"<<endl;
// new ResHeavy object, created by copy ctorResHeavy rh3(rh2);  // two copy of int[]
if(rh3.is_up_valid())cout<<"rh3 valid"<<endl;elsecout<<"rh3 invalid"<<endl;}

输出如下:

default ctormove ctorrh invalidrh2 validcopy ctorrh3 valid

我们可以看到std::movemove constructor使得转换资源变得容易。

std::move还有什么用?

std::move在对元素数组进行排序时也很有用。许多排序算法(例如选择排序和冒泡排序)通过交换元素对来工作。以前,我们不得不求助于复制语义学来进行交换。现在我们可以使用移动语义学,这更有效。

如果我们想将一个智能指针管理的内容移动到另一个智能指针,它也很有用。

引用:

这是一个完整的示例,使用std::移动(简单)自定义向量

预期产出:

 c: [10][11]copy ctor calledcopy of c: [10][11]move ctor calledmoved 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;}

std::move本身不做任何事情,而不是static_cast。根据cppreference.com

它完全等效于右值引用类型的static_cast。

因此,这取决于你在move之后分配给变量的类型,如果类型有constructorsassign operators接受右值参数,它可能会也可能不会窃取原始变量的内容,因此,它可能会将原始变量留在unspecified state中:

除非另有指定,否则所有已移动的标准库对象都将不再处于有效但未指定的状态。

因为对于内置的文字类型(如整数和原始指针)没有特殊的move constructormove assign operator,所以,它只是这些类型的简单副本。