总结一下C++11中涉及到左值引用和右值引用的场景。
引用:别名,生命时必须初始化、通过引用修改变量值。
左值引用
定义:对左值的引用。目的是避免内存拷贝,类似c中的指针,两个场景:函数传参、函数返回值。
意外:左值引用也可以引用右值,但是限定为const T&,这样右值就不能修改,如果必须修改,就只能使用右值引用,这也是右值引用发明的初衷之一。
右值引用
定义:对右值的引用。两个场景:实现移动语义、实现完美转发。右值引用如果想指向左值,也就是左值转化为右值,通过std::move。
引用虽然分为左值引用和右值引用,这是它的属性,但是这个变量是一个左值。
可以放在=左边,内存可寻址、具名。eg:变量名、返回左值引用的函数调用、前置自增自减。
只能放在=右边,不可寻址、不具名。
两种类型: 纯右值:字面量、返回非引用类型的函数调用、后置自增自减、算数表达式、逻辑表达式、 比较表达式
将亡值:C++11新引入的右值引用(帮助实现移动语义)相关的值类型。触发移动构造和移动赋值构造。
将亡值:用来触发移动构造和移动赋值构造,进行资源转移,之后,调用析构函数销毁。主要的职责是实现资源转移。(资源:IO、FD、堆内存、等)
在C++11之前,通过已有对象给新对象赋值,使用的是深拷贝。但是在某些场景下,比如IO、FD、堆内存,或者unique_ptr,这种资源是对象独占的,不能二次拷贝,需要从旧的对象转移到新的对象中,这个时候就需要使用右值作为形参,创建移动构造函数或者移动赋值运算法,将旧对象的资源转移的新对象,就对象变为将亡值去析构。
看下拷贝构造和移动构造的使用
- int main(int argc, char** argv ){
- cout << "--- copy construction ---" <
- A a;
- A b(a);
- cout << "--- move construction ---" <
-
- A c;
- A d(std::move(c));
- return 0;
- }
- ============== 输出 ==============
- (base) ➜ test ./a
- --- copy construction ---
- A():p = 0x11e6067e0
- A(const A&):p = 0x11e606810
- --- move construction ---
- A():p = 0x11e606840
- A(A&&):p = 0x11e606840
- ~A() 1
- ~A() 2
- ~A() 1
- ~A() 1
2.2 完美转发
什么是完美转发?
函数模板,可以将自己的参数完美地转发给内部调用的其他函数。
完美指:不仅能准确地转发参数的值,还能保证被转发的参数的左右值属性不变。
如下:在函数模板中的t,不仅能把t的值转发给func函数调用,同时还能把t的左值右值属性传进来。如果t的属性是左值,调用第一个func,如果t的属性是右值,调用第二个func。
- void func(int& t){
- cout << "lvalue:"<< t <
- }
-
- void func(int&& t){
- cout << "rvalue:"<< t <
- }
-
- template<typename T>
- void revoke(T && t){
- func(std::forward
(t)); - }
怎么解决的完美转发?
1 借用了万能引用
上述代码的T&&就是万能引用,要么使用类型推到auto,要么使用模板参数。具体的类型+&&是右值引用。通过引用的方式接收左右属性的值。
- void func(int& t){
- cout << "lvalue:"<< t <
- }
- void func(int&& t){
- cout << "rvalue:"<< t <
- }
- template<typename T>
- void revoke(T && t){ // 接收。不管t的属性是左值引用还是右值引用,t本身始终是一个左值。
- func(std::forward
(t));//转发 - }
- int main(int argc, char** argv )
- {
- int i = 10;
- revoke(10);// 解析为右值
- revoke(i);// 解析为 int& 左值引用
- }
- ========= 输出 =========
- (base) ➜ test ./a
- rvalue:10
- lvalue:10
万能是如何正确解析引用的属性的?
引用折叠:只处理接收参数的问题
参数是左值或左值引用,T& && (第一个是值的属性,后两个来自万能引用折叠) 转化为T&
参数是右值或右值引用,T&& &&(第一个是值的属性,后两个来自万能引用折叠)转化为T&&
本质:提取参数的属性,看是左值还是右值。
- // 只有在模板推到中,才允许多个引用符号
- revoke(static_cast<int & n>);// int& && t --> int& t
- revoke(static_cast<int && n>);// int&& && t --> int&& t
std::forward(v)
主要作用如下:
-
完美转发(Perfect Forwarding):std::forward 主要用于在泛型代码中完美转发参数。当你在一个模板函数中接收参数并希望将它们传递给另一个函数时,你可以使用 std::forward 来保持参数的值分类。这样可以确保参数被转发到下游函数时保持原始的左值或右值属性,从而避免额外的拷贝或移动操作。
-
保留值分类(Preserve Value Category):std::forward 可以确保参数在传递过程中保持其原始的值分类,即左值保持为左值,右值保持为右值。这对于支持完美转发的函数模板至关重要。
-
针对1和2也可以从解引用的角度理解,将左(右)值引用的引用解除,得到左(右)值。
综上:main函数中,将左值或者右值传递给revoke,revoke形参是万能引用,通过引用折叠确保可以同时接收和保持实参的值属性,但是t本身实际是一个左值,如何提取其指向的值的左值和右值属性呢?通过forward来保留值属性,传递给下游的函数。。
参考测试代码:
- #include
-
- using namespace std;
-
-
-
- class A{
- public:
- A(){
- p = new int[10];
- cout << "A():p = " << p <
- }
- // 拷贝构造函数:深拷贝
- A(const A& a){
- this->p = new int[10];
- memcpy(this->p, a.p, 10 * sizeof(int));
- cout << "A(const A&):p = " << p <
- }
- ~A(){
- if(p != nullptr){
- delete [] p;
- p = nullptr;
- cout << "~A() 1" <
- }
- else{
- cout << "~A() 2" <
- }
- }
-
- A(A&& a){
- this->p = a.p;
- a.p = nullptr;
- cout << "A(A&&):p = " << p <
- }
- private:
- int* p;
- };
- int main(int argc, char** argv )
- {
- cout << "--- copy construction ---" <
- A a;
- A b(a);
- cout << "--- move construction ---" <
-
- A c;
- A d(std::move(c));
- return 0;
- }
-
-
相关阅读:
复习单片机:定时器/计数器部分(内含:1.51 单片机定时器原理 +2.51 单片机定时/计数器结构+3.定时器配置+4.代码部分+5.实验现象)
python爬虫之requests库post请求
20220729NOI模拟赛--考后总结
【Electron】electron与cljs的处理
python之字符串
clion qt导出dll给别的项目用
Python基础学习笔记1(AI Studio)
PXE高效批量网络装机
CSS Position定位(详解网页中的定位属性)
基于Springboot的新闻资讯发布系统设计与实现(源码+lw+部署文档+讲解等)
-
原文地址:https://blog.csdn.net/flying_ant2018/article/details/139215816