1.先理解下c++为什么会需要move?
---- 变量,所有权,以及性能优化。
2.变量
从实用的角度,变量可以分为两类:
- 没有指针或引用的变量,可以称为trivial type。 (c++11,平凡可复制的概念用的也是trivial这个词)
- 有指针或引用的变量,可以称handle type。
简化理解,trivial type数据,没有指针、引用直接放在栈上的,无须优化性能。
而handle type通常有两部分数据:第一部分可以放在栈上,第二部分在堆里(
栈上的数据——就叫做handle,用来控制(handle)堆上的数据。从这个角度看栈上的数据是堆数据的所有者(owner) )。
c++ 语法move一般针对的是handle type的变量。
为什么Python,JavaScript,Java等等都没有move这个概念呢?
因为在这些有GC的语言里面,它们处理handle type的时候,赋值既具有"move"的性质,又具有copy的性质——实际是将引用复制给另外一个变量。
比如下面JavaScript代码
let a = { name: "good"}; let b = a; b.name = "Bad"; // a,b的名字都变成了bad.
这里变量b与a引用着同样的对象,它们就是一个引用而已。
b = a就是将这个名字给了b变量,它们指代的是同一个东西。
当我们通过b修改数据,此时a引用着的对象自然而然就已经修改了。
显然,此时无所谓move动作,因为b跟a指代同样的对象,这是很直观的想法。
更准确的说法是copy 栈上的名字,根本没有发生任何所有权的转移概念。
我们看下 C++里面,类似的b = a,默认的是拷贝复制,而不是引用。
void test() { auto a = vector<string>{ "str1", "str2", "str3" }; auto b = a; b[0] = "b string"; for (auto v : a) { cout << v << ", "; } cout << endl; for (auto v : b) { cout << v << ", "; } cout << endl; }
输出:
str1, str2, str3,
b string, str2, str3,
对比js的引用a、b,都在栈上,天然的指向同一个堆空间,只是别名。
而c++的变量a、b,都在栈上,指向了不同的堆空间,b=a,是复制了一份给到b指向的空间。
(像java,JS等有GC的语言为了更好地管理内存,基本不做堆栈的区分——不用关心是在栈上还是堆里。GC帮忙管理着内存,这是这些语言没有move概念的原因)
上面JS例子只拷贝了栈上面的引用(它们无须操作堆复制),而C++既拷贝了栈那部分数据也拷贝了堆里面的内容
如果,c++想实现所有权的转换呢,因此,C++11 引入了move的概念,我们只拷贝栈上的数据的时候,并引用到已有的堆数据(不另外分配堆内存并拷贝堆数据,提高性能)。
3.value categories也做了调整,从而规定哪些value可以被move
(左值、右值、将亡值)
- prvalue - 纯右值 。就是用于计算的或者用于初始化对象的。(pure)
- xvalue - 将亡值。就是临时变量。它是快要被销毁的值。(expiring)
-
lvalue - 左值。在内存中具有位置的值。所有具名变量都是左值 (left)
xvalue可以被直接move。
prvalue被move的时候,可以理解生成了一个临时变量xvalue,并move了这个临时变量。
lvalue,则需要使用std::move将lvalue变成xvalue,并move了这个xvalue。
4.c++中的move
std::string a = "stringaaa";
auto b = std::move(a);
cout << b << endl;
原来变量a指代的值的所有权转移给了b,变量a被置成“空”了move的场景例子。
- 接管资源
void My::take(Book && iBook) { mBook = std::move(iBook); //将没人要的iBook,拿过来据为己有 }
2. 转移所有权
auto thread = std::thread([]{}); std::vector<std::thread> lThreadPool; lThreadPool.push_back(std::move(thread)); //现在thread pool来掌控着thread
3. 避免拷贝
void f() { std::vector v = ...; take(std::move(v)); // 直接move进了函数take里面,不用拷贝 }
move是转移对象的所有权,所以我们可以move的是可以转移所有权的对象。
可以移动右值,也可以把左值通过std::move变成右值,从而可以实现移动。