1、区分左值与右值的区别
左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址。
变量可以取地址,所以是左值,但是常量和临时对象等不可以取地址,所以是右值。
左值是表达式结束后依然存在的持久对象(代表一个在内存中占有确定位置的对象)。
右值是表达式结束时不再存在的临时对象(不在内存中占有确定位置的表达式)。
所有的具名变量或者对象都是左值,而右值不具名。
对表达式取地址,如果能,则为左值,否则为右值。
常见的右值:“abc",123等都是右值。
右值引用,可以延长右值的生命周期
1 2 3
| int&& i = 123; int&& j = std::move(i); int&& k = i;
|
2、Move
用法1 转移所有权
将快要销毁的对象转移给其他变量,这样可以继续使用这个对象,而不必再创建一个一样的对象,省去了创建新的一样内容的对象,也就提高了性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <utility> #include <iostream> #include <vector> #include <string>
int main () { std::string foo = "foo-string"; std::string bar = "bar-string"; std::vector<std::string> myvector;
myvector.push_back (foo); myvector.push_back (std::move(bar));
std::cout << "myvector contains:"; for (std::string& x:myvector) std::cout << ' ' << x; std::cout << '\n';
return 0; }
|
用法2 改变所有者
对象的管理问题:编程语言的move是改变物体(值)的所有权,而物体(值)在内存中没有变动过(存储在堆里那部分数据没有变动,在栈上的数据被拷贝了)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class Array { public: Array() {} Array(const Array & temp_array) { size_ = temp_array.size_; data_ = new unsigned char[size_]; std::memcpy(data_, temp_array.data_, size_);
}
Array& operator=(const Array& temp_array) { delete[] data_; size_ = temp_array.size_; data_ = new unsigned char[size_]; std::memcpy(data_, temp_array.data_, size_);
}
Array(Array&& temp_array) { data_ = temp_array.data_; size_ = temp_array.size_; temp_array.data_ = nullptr; }
public: unsigned char* data_; int size_; };
cv::Mat matImage = cv::imread("230814images/Image_20230814163826944.bmp", cv::IMREAD_COLOR); Array a; a.data_ = matImage.data; a.size_ = matImage.rows * matImage.cols * matImage.channels(); int size2 = matImage.rows * matImage.step;
Array c = a;
Array b(std::move(a));
|
用法3 高效变量赋值
将vector B赋值给另一个vector
A,如果是拷贝赋值,那么显然要对B中的每一个元素执行一个copy操作到A,如果是移动赋值的话,只需要将指向B的指针拷贝到A中即可,若vector内元素复杂,数量大,则凸显出的性能优化则更加明显。
下面对比拷贝赋值与移动语义的耗时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int nTestTimes = 100; double dAllTimes = 0; for (int i = 0; i < nTestTimes; i++) { std::vector<int> vb(100, 2);
auto start1 = std::chrono::steady_clock::now(); std::vector<int> va = std::move(vb); auto end1 = std::chrono::steady_clock::now(); auto fTimeInfer = std::chrono::duration<double, std::milli>(end1 - start1).count(); dAllTimes += fTimeInfer; } double dOneTime = dAllTimes / nTestTimes; std::cout << "allTime:" << dAllTimes << "oneTime:" << dOneTime << std::endl;
|
可以看出,时间使用移动语义相比拷贝时间少了一半。
3、万能引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| template<typename T> void funcTest(T& param) { std::cout << param << std::endl; }
template<typename T> void funcTest2(T& param) { std::cout << "传入的是左值" << std::endl; } template<typename T> void funcTest2(T&& param) { std::cout << "传入的是右值" << std::endl; }
template<typename T> void funcTest3(T&& param) { std::cout << param << std::endl; }
int main() { std::cout << "Hello World!\n";
if (1) { int num = 2023; funcTest(num);
funcTest2(num); funcTest2(2023);
funcTest3(num); funcTest3(2023); } }
|
测试1定义了左值引用的模板函数,使用常量2023作为形参输入就导致报警;测试2定义了2个模板函数;测试3使用万能引用,针对输入变量类型自动推理出左值或右值,需要注意:只有发生类型推导的时候,T&&才表示万能引用(如模板函数传参就会经过类型推导的过程)
4、拓展
完美转发std::forward
引用折叠
参考: https://juejin.cn/post/7192171206030819385
https://en.cppreference.com/w/cpp/utility/move
https://cloud.tencent.com/developer/article/1901799
https://www.cnblogs.com/shadow-lr/p/Introduce_Std-move.html
https://zhuanlan.zhihu.com/p/94588204