C++ std::move
500 Words|Read in about 3 Min|本文总阅读量次
Android源码中有一些经常会遇到的c++基础的内容,笔者对std::move函数进行简单熟悉。
C++ std::move
开篇三连问
- 为什么会引入
std::move
这个函数- 另外什么是左值,什么是右值
- 有哪些是右值引用参数的函数
0前言
1class DataClass {
2public:
3 DataClass(int size) : size(size) {
4 data = new char[size];
5 memset(data, 0, size);
6 }
7
8 void setData(const char* string)
9 {
10 std::cout<< "Data len " << strlen(data) << std::endl;
11 if(!strlen(data))
12 {
13 memmove(data, string, size);
14 }
15 std::cout<< "Data string " << data << std::endl;
16 }
17
18 // 深拷贝构造
19 DataClass(const DataClass& temp_array) {
20 std::cout<< " DataClass deep copy " << temp_array.data << std::endl;
21 size = temp_array.size;
22 data = new char[size];
23 for (int i = 0; i < size; i ++) {
24 data[i] = temp_array.data[i];
25 }
26 }
27
28 // 深拷贝赋值
29 DataClass& operator=(const DataClass& temp_array) {
30 std::cout<< " DataClass deep operator " << temp_array.data << std::endl;
31 delete[] data;
32
33 size = temp_array.size;
34 data = new char[size];
35 for (int i = 0; i < size; i ++) {
36 data[i] = temp_array.data[i];
37 }
38 return *this;
39 }
40
41 ~DataClass() {
42 std::cout << "start ~DataClass" << std::endl;
43 delete[] data;
44 }
45
46private:
47 char *data;
48 int size;
49};
这是一个数据类,我需要一个数据类的对象直接使用之前的对象内容,这个时候往往需要一次深拷贝,比如拷贝构造函数,或者是运算符拷贝函数等。
1int main()
2{
3 DataClass a(12);
4 a.setData((char*)"Hello, Iron");
5 DataClass b(a);//拷贝构造,深拷贝
6 DataClass c(12);
7 c = a;//运算符拷贝,深拷贝
8 return 0;
9}
如果说需要的一个数据类的对象直接使用之前的对象内容,并且之前的对象可以释放,那么可以采用移动函数。
移动函数可以将之前对象的内容移到新的对象中来。
1//如果使用浅拷贝来作为移动函数,往往会有问题
2DataClass(const DataClass& temp_array) {
3 data = temp_array.data;
4 size = temp_array.size;
5 // 为防止temp_array析构时delete data,提前置空其data
6 temp_array.data = nullptr;
7}
会提示Variable 'temp_array' declared const here cannot assign to variable 'temp_array' with const-qualified type 'const DataClass &'
,即不能直接修改传参temp_array。
无法实现!temp_array
是个const左值引用,无法被修改,所以temp_array.data_ = nullptr;
这行会编译不过。当然函数参数可以改成非const:DataClass(DataClass & temp_array){...}
,这样也有问题,由于左值引用不能接右值,DataClass a = DataClass(DataClass());
这种调用方式就没法用了。
这个时候需要一个右值的引用来解决这个问题。
使用方式
1//左值a,用std::move转化为右值
2DataClass c(std::move(a));
1左值右值
左值是表达式结束后依然存在的持久对象(代表一个在内存中占有确定位置的对象)
右值是表达式结束时不再存在的临时对象(不在内存中占有确定位置的表达式)
便携方法:对表达式取地址,如果能,则为左值,否则为右值
1int val;
2val = 4; // 正确 ①
34 = val; // 错误 ②
运算符=要求等号左边是可修改的左值,4是临时参与运算的值,一般在寄存器上暂存,运算结束后在寄存器上移除该值,故①是对的,②是错的。一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址**。**
左值引用
左值引用的基本语法
1T& TypeName = 左值表达式;
右值引用
右值引用基本语法
1T&& TypeName = 左值表达式;
而std::move函数
主要可以将一个左值转换成右值引用,从而可以调用C++11右值引用的拷贝构造函数
剖析std::move
源码
转发左值,将左值引用转化成右值引用
1template<typename _Tp>
2constexpr _Tp&&
3forward(typename std::remove_reference<_Tp>::type& __t) noexcept
4{ return static_cast<_Tp&&>(__t); }
转发右值,将右值引用转化成右值引用
1template<typename _Tp>
2constexpr _Tp&&
3forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
4{
5 static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
6 " substituting _Tp is an lvalue reference type");
7 return static_cast<_Tp&&>(__t);
8}
实际上,std::move
被广泛用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。
2右值引用参数的函数
在STL的很多容器中,都实现了以右值引用为参数的移动构造函数和移动赋值重载函数。
最常见的是vector数组中的推数据函数,push_back
和emplace_back
,目前更多使用的是emplace_back
。
参数为左值引用意味着拷贝,为右值引用意味着移动。
1#include <iostream>
2#include <vector>
3
4int main() {
5 std::string str1 = "20220828";
6 std::vector<std::string> v;
7
8 v.push_back(str1); // 1.传统方法,copy
9 std::cout << v.back() << std::endl;
10 v.push_back(std::move(str1)); // 2.调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串
11 std::cout << v.back() << std::endl;
12 v.emplace_back(std::move(str1)); // 3.emplace_back效果相同,str1会失去原有值
13 std::cout << v.back() << std::endl;
14 v.emplace_back("20220829"); // 当然可以直接接右值
15 std::cout << v.back() << std::endl;
16}
结果
查看源码可以得知
1//stl_vector.h
2//第1个函数的原型,是一个左值引用
3void push_back(const value_type& __x)
4{
5 ...
6}
7
8//第2个函数的原型,是一个右值引用,实际调用的是emplace_back
9#if __cplusplus >= 201103L
10void push_back(value_type&& __x)
11{
12 emplace_back(std::move(__x));
13}
14
15//stl_bvector.h
16void emplace_back(_Args&&... __args)
17{
18 push_back(bool(__args...));
19 #if __cplusplus > 201402L
20 return back();
21 #endif
22}
因此,可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move
触发移动语义,提升性能。
3解答
为什么会引入
std::move
这个函数
STL
和自定义类中,左值转换成右值引用实现移动语义,避免深拷贝,从而提升程序性能另外什么是左值,什么是右值
通俗来讲左值是位于运算符左边,右值是运算符右边的值。
左值是表达式结束后依然存在的持久对象(代表一个在内存中占有确定位置的对象)
右值是表达式结束时不再存在的临时对象(不在内存中占有确定位置的表达式)
有哪些是右值引用参数的函数
STL
中常见的函数都有右值引用参数的函数,比如vector里面的emplace_back
,unique_ptr
例如
1std::unique_ptr<A> ptr_a = std::make_unique<A>(); 2std::unique_ptr<A> ptr_b = std::move(ptr_a); // unique_ptr只有移动赋值重载函数,参数是&& ,只能接右值,因此必须用std::move转换类型 3std::unique_ptr<A> ptr_b = ptr_a; // 编译不通过