【C++】右值引用與完美轉發

目錄

一、右值引用:

1、左值與右值:

2、左值引用和右值引用:

二、右值引用的使用場景:

1、左值引用的使用場景:

2、右值引用的使用場景:

移動構造

移動賦值

三、完美轉發:

1、萬能引用:

2、實際使用:


一、右值引用:

1、左值與右值:

在了解右值引用前,要先了解什么是左值引用(其實在之前已經使用過很多回了),那么要了解什么是左值引用,就先要了解什么是左值什么是右值

在等號左邊的值叫左值嗎?在等號右邊的值叫右值嗎??

顯然定義不會這么簡單的,但是左值可以出現賦值符號的左邊,右值不能出現在賦值符號左邊,也就是說在等號左邊的值一定不是右值,在等號右邊的可以是左值或右值

左值:

能夠進行取地址的操作,也可以被修改

如下的a,b,c都是左值

int main()
{int a = 10;const int b = 10;int* c = new int(0);return 0;
}

右值:

不能夠進行取地址的操作,一般不能夠被修改

如下,10,x,fmin(x,y)的返回值就是右值

int main()
{double x = 1.1, y = 2.2;//如下就是右值10;x + y;fmin(x, y);return 0;
}

理解:

1、右值的本質是一個臨時變量或者常量值
2、這些臨時變量是沒有被實際存儲起來的,所以無法對右值取地址????????
3、像上述的fmin的返回值,其實際上就是一份臨時拷貝,所以算作右值

2、左值引用和右值引用:

什么是左值引用:

左值引用就是給左值取別名

int main()
{int a = 10;const int b = 10;int* c = new int(0);//這就是左值引用int& pa = a;const int& pb = b;int*& pc = c;return 0;
}

什么是右值引用:

右值引用就是給右值取別名

int main()
{double x = 1.1, y = 2.2;//如下就是右值10;x + y;fmin(x, y);//這個就是右值引用int&& p1 = 10;int&& p2 = x + y;int&& p3 = fmin(x, y);return 0;
}

這里在給右值取別名后,右值會被存儲到特定的位置,此時就能夠取到該位置的地址了

左值引用可以引用右值嗎 ----- 可以

但是,左值引用不能直接引用右值,因為右值不能夠被修改,左值可以修改,如果直接引用的話權限會存在放大問題,所以如果想要左值引用右值就需要加上const修飾

像我們之前在函數參數傳參的時候經常寫const T& x,這就是保證既能夠傳左值,又能夠傳右值

template<class T>
void func(const T& val)
{cout << val << endl;
}
int main()
{string s("111");func(s);       //s為左值func("222"); //"222"為右值return 0;
}

右值引用可以引用左值嗎 ----- 可以

但是,右值引用也不能直接引用左值,如果想要引用左值,就需要加上move后的左值

int main()
{int a = 10;//右值引用給左值取別名int&& pa = move(a);return 0;
}

為什么加上move后才能讓右值引用來引用左值呢?

我們首先要知道,左值引用或者右引用都是在給資源取別名,對于左值引用,就是直接指向原本的數據,對于右值引用,我們知道原本是沒有空間資源的,那么右值引用引用右值就是首先開辟一塊空間,然后將常量或者臨時變量轉移到開辟好的地方,然后在指向該地方

所以右值引用的本質是對右值進行資源的轉移

此時就有空間資源了,此時就能夠取地址了,并且能夠對其進行修改了

對于常量,臨時變量,表達式的結果這些右值,編譯器在右值引用的時候會直接將這些右值進行轉移資源,但是對于左值,編譯器不敢直接轉移,這個時候編譯器就為用戶提供了一個函數move,當進行move左值的時候,就能夠讓右值引用 引用左值了

二、右值引用的使用場景:

1、左值引用的使用場景:

左值引用既能夠引用左值,又能夠引用右值,但是還是存在短板,所以在C++11里面,引入了右值引用來彌補左值引用的短板

在左值引用中:

1、左值引用做參數,防止傳參是的拷貝
2、左值引用做返回值,防止返回時對返回對象進行拷貝

首先,寫一個自己的string類,在里面寫上部分cout來方便打印觀察

namespace ppr
{class string{public:typedef char* iterator;iterator begin(){return _str;//返回字符串中第一個字符的地址}iterator end(){return _str + _size;//返回字符串中最后一個字符的后一個字符的地址}//構造函數string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//交換兩個對象的數據void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷貝構造函數(現代寫法)string(const string& s):_str(nullptr),_size(0),_capacity(0){cout << "string(const string& s) -- 深拷貝" << endl;string tmp;swap(tmp);}//移動構造函數(現代寫法)string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移動構造" << endl;swap(s);}//賦值運算符重載(現代寫法)string& operator=(const string& s){cout << "string& operator=(const string& s) -- 深拷貝" << endl;string tmp;swap(tmp);return *this;}//析構函數~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}//[]運算符重載char& operator[](size_t i){assert(i < _size);return _str[i];}//改變容量,大小不變void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strncpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}//尾插字符void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;}//+=運算符重載string operator+=(char ch){push_back(ch);string tmp(*this);return tmp;}//返回C類型的字符串const char* c_str()const{return _str;}private:char* _str;size_t _size;size_t _capacity;};
}

接著看看左值引用的使用場景

//首先,看看左值引用的使用場景
//值傳參
void func1(ppr::string s)
{cout << "void func1(ppr::string s)" << endl;
}
//左值引用傳參
void func2(const ppr::string& s)
{cout << "void func2(const ppr::string& s)" << endl;
}int main()
{ppr::string ret1("1111111111111");func1(ret1);//這里采用深拷貝func2(ret1);ret1 += '0';//里面return *this 的時候,也會進行拷貝構造return 0;
}

其中,在func1的時候,傳值傳參會進行一次拷貝構造,在+=那里,返回* this的時候,也會進行一次拷貝構造這樣的話會看到兩次深拷貝

左值引用短板:

左值引用能夠避免不必要的拷貝構造,但是并不能完全避免

左值引用做參數的時候,能夠完全避免傳參時的拷貝
左值引用做返回值的時候,不能完全避免拷貝

比如如果返回的是一個局部變量,在返回的時候局部變量被銷毀了,此時如果使用左值引用進行返回,就會返回的野指針,此時就不能夠用左值引用返回,需要老老實實地值拷貝

如下會進行兩次拷貝操作,然后將ret返回

如果在新一點的編譯器會進行優化,只需進行一次拷貝操作

如果是引用傳參,此時局部變量的局部空間,在出了函數作用域之后就會被釋放,此時就會出問題

所以,C++11為了解決這類問題,提出了右值引用來解決這種場景

2、右值引用的使用場景:

右值分為 純右值將亡值

純右值:內置類型的右值
將亡值:自定義類型的右值

右值引用和移動語句解決上述問題的方式就是,給當前模擬實現的string類增加移動構造方法

移動構造

移動構造本質是將參數右值的資源竊取過來,占位已有,那么就不用做深拷貝了,所以它叫做移動構造,就是竊取別人的資源來構造自己

如果沒加上述的移動拷貝,就會出現深拷貝

如果加上移動構造,那么就會走移動構造函數,這樣就能更加減少拷貝

移動構造的本質就是將參數的右值竊取過來,占為己有,這樣它就不用再深度拷貝了,所以叫做移動構造

移動構造和拷貝構造的區別:

1、在沒有增加移動構造之前,由于拷貝構造采用的是const左值引用接收參數,因此無論拷貝構造對象時傳入的是左值還是右值,都會調用拷貝構造函數
2、增加移動構造之后,由于移動構造采用的是右值引用接收參數,因此如果拷貝構造對象時傳入的是右值,那么就會調用移動構造函數
3、string的拷貝構造函數做的是深拷貝,而移動構造函數中只需要調用swap函數進行資源的轉移,因此調用移動構造的代價比調用拷貝構造的代價小

左值引用:直接引用對象以減少拷貝

右值引用:間接減少拷貝,將臨時資源等將亡值的資源通過 移動構造 進行轉移,減少拷貝

移動賦值

移動賦值是一個賦值運算符重載函數,該函數的參數是右值引用類型的,移動賦值也是將傳入右值的資源竊取過來,占為己有,這樣就避免了深拷貝,之所以它叫移動賦值,就是竊取別人的資源來賦值給自己的意思

// 賦值重載
string& operator=(const string& s) 
{cout << "string& operator=(string s) -- 深拷貝" << endl; string tmp(s); swap(tmp); return *this;
}
//移動賦值
string& operator=(string&& s) 
{cout << "string& operator=(string && s) -- 移動拷貝" << endl; swap(s); return *this;
}

string& operator=(const string& s) 和string& operator=(string&& s) 的區別:

1、在沒有string& operator=(string&& s) 的時候,如果進行=操作,那么無論是左值還是右值傳參都會調用string& operator=(const string&?s) 這個函數

2、在增加移動賦值后,如果是左值就調用原來的函數,如果是右值就調用新加的移動賦值函數

3、移動賦值函數是通過swap函數進行資源的交換,而原來的operator=是通過深拷貝進行,因此,移動賦值的代價比原來的要小

三、完美轉發:

1、萬能引用:

template<class T>
void PerfectForward(T&& t)
{//...
}

這里函數中的參數并不是右值引用,如果傳的模板是左值,這里的參數就是左值引用,相反如果傳的模板是右值,那么這里的參數就是右值引用

void func(int& a)
{cout << "左值引用" << endl;
}
void func(const int& a)
{cout << "const 左值引用" << endl;
}
void func(int&& a)
{cout << "右值引用" << endl;
}
void func(const int&& a)
{cout << "const 右值引用" << endl;
}
template<class T>
void perfectForward(T&& val)
{func(val);
}int main()
{int a = 10;perfectForward(a); //左值const int b = 10;  //const 左值perfectForward(b);perfectForward(move(a)); // 右值perfectForward(move(b)); //const 右值return 0;
}

如上,這就是通過func函數重載,來觀察編譯器會怎樣進行函數調用

如上,這是運行結果,為什么會這樣呢?難道是編譯器做的不對嗎,在實際調用中,4個函數沒有一個是進入了右值引用,均匹配的是左值引用版本,這是為什么呢?

當對右值進行引用后,會導致右值被存儲到特定的位置,此時就能夠對這個引用后的右值進行取地址了,這樣的話,這個右值就模版被識別成左值了

也就是說,在右值引用過一次后,會導致右值變成左值,但是如果想要繼續保證其右值的屬性,此時就需要用到完美轉發

如上,在對右值引用進行傳參的時候,在前面加上forward<T>,這樣經過完美轉發后,調用PerfectForward函數時傳入的是右值就會匹配到右值引用版本的Func函數,傳入的是const右值就會匹配到const右值引用版本的Func函數

forward是一個模板函數,需要指定模板參數類型T,確保能正確推導并傳遞

2、實際使用:

首先實現一個建議的list,在其中實現左值引用的push_back和insert函數

namespace ppr
{template<class T>struct ListNode{T _data;ListNode* _next = nullptr;ListNode* _prev = nullptr;};template<class T>class list{typedef ListNode<T> node;public://構造函數list(){_head = new node;_head->_next = _head;_head->_prev = _head;}//左值引用版本的push_backvoid push_back(const T& x){insert(_head, x);}//右值引用版本的push_backvoid push_back(T&& x){insert(_head, x);}//左值引用版本的insertvoid insert(node* pos, const T& x){node* prev = pos->_prev;node* newnode = new node;newnode->_data = x;prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}//右值引用版本的insertvoid insert(node* pos, T&& x){node* prev = pos->_prev;node* newnode = new node;newnode->_data = x;prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}private:node* _head; //指向鏈表頭結點的指針};
}

接著進行左值和右值的push_back版本的調用

int main()
{ppr::list<ppr::string> lt;ppr::string s1("111111111111111");//左值的push_backlt.push_back(s1);cout << endl << endl;ppr::string s2("111111111111111");//右值的push_backlt.push_back(move(s2));cout << endl << endl;lt.push_back("22222222222222222");//右值的push_backreturn 0;
}

但是會發現全部都是深拷貝,這和上述右值被引用后,就可以取地址了,就變成左值了,所以為了避免這種情況,就需要在右值版本的push_back和insert加上完美轉發,讓右值能夠保存右值屬性

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/73924.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/73924.shtml
英文地址,請注明出處:http://en.pswp.cn/web/73924.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

wx201基于ssm+vue+uniapp的購物系統設計與實現小程序

開發語言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服務器&#xff1a;tomcat7數據庫&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;數據庫工具&#xff1a;Navicat11開發軟件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

Mac 常用命令

一、文件操作(必知必會)? ?1. 快速導航 cd ~/Documents # 進入文檔目錄 cd .. # 返回上級目錄 pwd # 顯示當前路徑 2. ?文件管理 touch new_file.txt # 創建空文件 mkdir -p project/{src,docs} # 遞歸創建目錄 cp …

Nginx RTMP 處理模塊 (ngx_rtmp_handler.c) 詳細分析

ngx_rtmp_handler 是 Nginx RTMP 模塊中的核心處理部分&#xff0c;主要負責處理 RTMP 流會話中的數據接收、發送、ping 操作以及分塊大小的設置等。 1. 全局變量 ngx_rtmp_naccepted: 記錄接受的 RTMP 連接數。 ngx_rtmp_bw_out 和 ngx_rtmp_bw_in: 分別表示輸出帶寬和輸入帶…

(二)萬字長文解析:deepResearch如何用更長的思考時間換取更高質量的回復?各家產品對比深度詳解

DeepResearch的研究背景 業務背景&#xff1a;用更長的等待時間&#xff0c;換取更高質量、更具實用性的結果 當前AI技術發展正經歷從“即時響應”到“深度思考”的范式轉變。用戶對延遲的容忍度顯著提升&#xff0c;從傳統200ms的交互響應放寬至數秒甚至數分鐘&#xff0c;以…

綜述速讀|086.04.24.Retrieval-Augmented Generation for AI-Generated Content A Survey

論文題目&#xff1a;Retrieval-Augmented Generation for AI-Generated Content: A Survey 論文地址&#xff1a;https://arxiv.org/abs/2402.19473 bib引用&#xff1a; misc{zhao2024retrievalaugmentedgenerationaigeneratedcontent,title{Retrieval-Augmented Generation…

Spring Cache:簡化緩存管理的抽象框架

Spring Cache Spring Cache是Spring框架提供的緩存抽象層&#xff0c;通過注解和自動化配置&#xff0c;簡化應用中對緩存的操作&#xff0c;支持多種緩存實現&#xff08;如Redis、Ehcache、Caffeine&#xff09;。 1. 核心特性 聲明式緩存&#xff1a;通過注解&#xff08;…

求矩陣某列的和

設計函數sum_column( int A[E1(n)][E2(n)], int j )&#xff0c;E1(n)和E2(n)分別為用宏定義的行數和列數&#xff0c;j為列號。在該函數中&#xff0c;設計指針ptr&A[0][j]&#xff0c;通過*ptr及ptrptrE2(n)訪問第j列元素&#xff0c;從而求得第j列元素的和。在主函數中定…

IM騰訊Trtc與vod云點播:實現合流錄制并上傳,根據參數返回視頻地址

全文目錄,一步到位 1.前言簡介1.1 專欄傳送門1.1.1 文檔傳送門 2. java基礎使用2.1 準備工作2.1.1 云控制臺獲取(密鑰和密鑰secret)2.1.2 找到trtc控制臺2.1.3 vod云點播控制臺 2.2 使用準備的數據進行操作2.2.0 引入依賴2.2.1 創建TrtcUtils工具類2.2.2 TrtcReqDTO 錄制請求dt…

藍橋杯 數三角

問題描述 小明在二維坐標系中放置了 n 個點&#xff0c;他想從中選出一個包含三個點的子集&#xff0c;使得這三個點能夠組成一個三角形。 由于這樣的方案太多了&#xff0c;他決定只選擇那些可以組成等腰三角形的方案。 請幫他計算出一共有多少種選法可以組成等腰三角形。 …

【Kafka】從理論到實踐的深度解析

在當今數字化轉型的時代&#xff0c;企業面臨著數據量呈指數級增長、業務系統愈發復雜的挑戰。在這樣的背景下&#xff0c;高效的數據傳輸與處理技術成為了關鍵。Kafka&#xff0c;作為一款分布式消息隊列系統&#xff0c;憑借其卓越的性能和豐富的特性&#xff0c;在眾多企業的…

Linux課程學習一

一.fopen與fclose函數 linux中fopen函數直接用man fopen 去查看 函數原型 FILE * fopen(constchar *path , cost char *mode) /* * description : 打開一個文件 * param ‐ path : 指定文件路徑,如&#xff1a;"./test.txt"&#xff0c;也可以直接由文件名 * param …

【區塊鏈安全 | 第十篇】智能合約概述

部分內容與前文互補。 文章目錄 一個簡單的智能合約子貨幣&#xff08;Subcurrency&#xff09;示例區塊鏈基礎交易區塊預編譯合約 一個簡單的智能合約 我們從一個基礎示例開始&#xff0c;該示例用于設置變量的值&#xff0c;并允許其他合約訪問它。 // SPDX-License-Identi…

XML標簽格式轉換為YOLO TXT格式

針對的是多邊形&#xff08;<polygon>&#xff09;來描述對象的邊界&#xff0c;而不是傳統的矩形框&#xff08;<bndbox>&#xff09; import xml.etree.ElementTree as ET import os from pathlib import Path# 解析VOC格式的XML文件&#xff0c;提取目標框的標…

大唐杯02 DTM.PX4.016

01 5G關鍵技術概述 回傳壓力大&#xff1a;核心網向基站回傳壓力大 02 5G關鍵技術介紹01

CSS3學習教程,從入門到精通, CSS3 盒子模型的詳細語法知識點及案例代碼(23)

CSS3 盒子模型的詳細語法知識點及案例代碼 CSS3 盒子模型完整指南 一、盒子模型基礎 每個 HTML 元素都被視為一個矩形盒子&#xff0c;由以下部分組成&#xff1a; 內容區 (Content)內邊距 (Padding)邊框 (Border)外邊距 (Margin) 二、語法知識點詳解 1. 盒子的寬和高 sel…

《Linux運維實戰:Ubuntu 22.04修改root用戶默認名并禁止登錄》

總結&#xff1a;整理不易&#xff0c;如果對你有幫助&#xff0c;可否點贊關注一下&#xff1f; 更多詳細內容請參考&#xff1a;Linux運維實戰總結 一、背景信息 由于安全方面的考慮&#xff0c;先要求Ubuntu 22.04系統重的root用戶禁止登錄&#xff0c;并修改用戶名root為ad…

docker-compose自定義網絡,解決docker-compose網段路由沖突

問題排查 先route一波查看一下路由表 容器路由19和堡壘機路由沖突 解決方案 更改docker網段更改docker生成容器的網段 > 基本操作 docker network ls &#xff1a;查看docker網絡列表 docker network inspect <network id/name>&#xff1a;查看某個docker網絡詳情…

前端 - ts - - declare聲明類型

在使用typeScript的項目中 需要聲明屬性類型 單獨的局部屬性 可以直接在當前文件中聲明 全局屬性需要在項目根目錄下新建.d.ts文件 vite會自動識別.d.ts類型文件 在該文件中使用declare聲明類型有三種寫法 1、在某種類型的文件中聲明 2、聲明window上的屬性類型 3、全局聲明…

[Mac]利用Hexo+Github Pages搭建個人博客

由于我這臺Mac基本沒啥環境&#xff0c;因此需要從零開始配置&#xff0c;供各位參考。 注意??&#xff1a;MacBook (M4)使用/bin/zsh作為默認Shell&#xff0c;其對應的配置文件為~/.zshrc 參考文檔&#xff1a; HEXO系列教程 | 使用GitHub部署靜態博客HEXO | 小白向教程 文…

運維面試題(十一)

1.如果一個硬盤 IO 時阻塞了&#xff0c;會發生什么情況&#xff1f; 進程/線程掛起&#xff1a;發起I/O操作的進程或線程會被操作系統置為阻塞狀態&#xff08;等待狀態&#xff09;&#xff0c;直到I/O完成。CPU資源釋放&#xff1a;阻塞的線程會讓出CPU&#xff0c;操作系統…