左值引用與右值引用

左值和右值

左值(lvalue):在表達式結束后仍然存在,可以取地址。簡單理解:有名字、有存儲位置。
比如變量、數組元素、對象等。

右值(rvalue):臨時值,表達式結束后就消失,不能取地址。
比如字面量、表達式的臨時結果。

int x = 10;      // x 是左值
int y = x;       // x 是左值,10 是右值
int z = x + y;   // (x + y) 是右值,z 是左值

左值引用

左值引用就是對 左值的引用。
語法:T& ref = var;

int a = 5;
int& ref = a;  // ref 是 a 的別名
ref = 10;      // 改變 ref 其實就是改變 a

?? 注意:左值引用不能直接綁定到右值上。

int& r = 5;   // ? 錯誤,5 是右值

右值引用

右值引用是 C++11 引入的,允許綁定到 右值。
語法:T&& ref = expr;

int&& r = 5;       // ? r 引用了一個臨時右值
int&& r2 = a + 3;  // ? a + 3 是右值

右值引用的意義:

可以延長臨時值的生命周期。

用于 移動語義(move semantics) 和 完美轉發(perfect forwarding)。

左值引用 vs 右值引用的函數參數示例

void f(int& x) {        // 接受左值std::cout << "左值引用\n";
}
void f(int&& x) {       // 接受右值std::cout << "右值引用\n";
}int main() {int a = 10;f(a);     // 傳左值,調用 f(int&)f(20);    // 傳右值,調用 f(int&&)
}
輸出:左值引用
右值引用

應用場景:移動語義

右值引用最重要的用途是避免不必要的拷貝,提高效率。

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v1 = {1,2,3,4,5};vector<int> v2 = std::move(v1); // 使用右值引用轉移資源cout << "v1 size: " << v1.size() << endl;cout << "v2 size: " << v2.size() << endl;
}
結果:v1 size: 0
v2 size: 5

這里 std::move 把 v1 轉換成右值引用,避免了數據拷貝,而是直接轉移所有權。

移動構造時發生了什么?

以 std::vector 為例,里面有三個關鍵成員:

指向堆區的指針 ptr

當前元素個數 size

容量 capacity

拷貝構造

會重新分配一塊堆內存,把 v1 的數據拷貝過去。

移動構造

直接把 v1 的內部指針 ptr 交給 v2。

然后把 v1 的 ptr 置空,size 和 capacity 設為 0。

所以:

v1 變成一個 空的 vector(但依然是合法對象)。

v2 擁有了原來 v1 的那塊內存。

v2 用完之后,內存會銷毀嗎?

是的。
當 v2 生命周期結束時,它會調用析構函數,釋放那塊堆內存。

而 v1 在 std::move 之后,它的 ptr 已經被清空,所以它的析構函數不會釋放任何東西(避免二次釋放)。

直觀理解(類比搬家 🚚)

v1 原來有一套家具(堆內存)。

std::move(v1) 把家具的所有權交給了 v2。

v1 自己變成了一個空房子(里面啥都沒有,但房子還在)。

當 v2 銷毀時,它負責把家具丟掉。

示例

  1. 問題:沒有移動語義時的性能問題
    假設我們有一個BigData類,它內部持有一個很大的動態數組(這里用std::vector模擬)。拷貝這個類的對象會非常昂貴,因為它需要分配新的內存并復制所有數據。
#include <iostream>
#include <vector>
#include <chrono>class BigData {
private:std::vector<int> m_data; // 模擬一個很大的數據塊public:// 構造函數,分配大量數據BigData(size_t size) : m_data(size) {std::cout << "Constructor called. Allocated " << size << " elements." << std::endl;}// 拷貝構造函數(深拷貝)- 性能瓶頸!BigData(const BigData& other) : m_data(other.m_data) {std::cout << "Copy Constructor called. Expensive deep copy!" << std::endl;}// 拷貝賦值運算符(深拷貝)BigData& operator=(const BigData& other) {if (this != &other) {m_data = other.m_data; // 又一次昂貴的拷貝!}std::cout << "Copy Assignment called. Expensive deep copy!" << std::endl;return *this;}// 析構函數~BigData() {std::cout << "Destructor called." << std::endl;}
};// 一個函數,返回一個BigData對象
BigData createBigData() {BigData data(1000000); // 在函數內部創建一個大對象return data; // 傳統C++03中,這里可能會觸發拷貝
}int main() {BigData my_data = createBigData(); // 這里期望得到函數內部創建的對象return 0;
}
  1. 解決方案:實現移動語義
    移動語義允許我們“竊取”即將被銷毀的對象的資源,而不是進行昂貴的拷貝。我們通過定義移動構造函數和移動賦值運算符來實現這一點。
#include <iostream>
#include <vector>
#include <utility> // for std::moveclass BigData {
private:std::vector<int> m_data;public:BigData(size_t size) : m_data(size) {std::cout << "Constructor called. Allocated " << size << " elements." << std::endl;}// 1. 移動構造函數 (參數是非常量右值引用 BigData&&)BigData(BigData&& other) noexcept // noexcept 很重要,用于標準庫優化: m_data(std::move(other.m_data)) // 關鍵:使用std::move移動內部的vector{std::cout << "Move Constructor called. Efficient move!" << std::endl;// 移動后,源對象‘other’的m_data現在處于有效但未狀態(通常是空)}// 2. 移動賦值運算符BigData& operator=(BigData&& other) noexcept {if (this != &other) {m_data = std::move(other.m_data); // 關鍵:移動賦值}std::cout << "Move Assignment called. Efficient move!" << std::endl;return *this;}// 保留拷貝構造和拷貝賦值,實現“Rule of Five”BigData(const BigData& other) = default;BigData& operator=(const BigData& other) = default;~BigData() = default;
};// 工廠函數
BigData createBigData() {BigData data(1000000);return data; // 編譯器意識到‘data’是局部對象,是“將亡值”// 優先選擇移動構造函數,即使沒有移動構造也會嘗試拷貝
}int main() {std::cout << "--- Scenario 1: Return from function ---" << std::endl;BigData my_data = createBigData(); // 調用移動構造函數(如果優化不掉)std::cout << "\n--- Scenario 2: Explicit std::move ---" << std::endl;BigData data1(1000);BigData data2 = std::move(data1); // 使用std::move將左值強制轉換為右值,// 從而調用移動構造函數。// data1此后不應再被使用!return 0;
}

代碼關鍵點解釋:
移動構造函數 BigData(BigData&& other):

參數類型是BigData&&,這是一個右值引用,它只能綁定到臨時對象或即將被銷毀的對象(“將亡值”)。

它的作用是“竊取”源對象other的資源。在這里,我們使用std::move(other.m_data)來移動其內部的vector。std::move的本質是一個static_cast,它將變量強制轉換為右值,從而觸發vector自身的移動構造函數。

noexcept關鍵字向標準庫承諾這個操作不會拋出異常,這很重要,因為標準庫容器(如std::vector)在重新分配內存時會優先使用noexcept的移動操作而不是拷貝操作來轉移元素,性能更高。

移動賦值運算符 operator=(BigData&& other):

原理同移動構造函數,用于在賦值時移動資源。

std::move:

它本身不移動任何東西!它只是一個轉換工具,告訴編譯器:“我知道這個左值對象我不再需要了,請把它當作一個右值來處理”。

真正的移動操作是在類的移動構造函數或移動賦值運算符中完成的。

Rule of Five:

如果你定義了析構函數、拷貝構造函數、拷貝賦值運算符中的任何一個,那么你應該定義全部五個(加上移動構造函數和移動賦值運算符)來精確管理資源。在上面的例子中,我們顯式定義了移動操作,并用= default保留了默認的拷貝操作。

現代編譯器非常智能,通常會直接進行“返回值優化”,連移動構造都省略了。但在更復雜的返回路徑中,移動語義是重要的保障。

實際應用:編譯器的“神來之筆”與程序員的“安全網”

想象一下你要從A城市(函數內)運送一批貴重家具(大數據)到B城市(函數外)。

沒有優化(C++03時代):你得先在A城找個倉庫(函數棧幀)放家具,然后雇輛卡車,把家具一件件搬上卡車(拷貝),運到B城后,再一件件卸下來放到新家。費時費力!

有移動語義(C++11基礎):你發現這些家具到了B城后,A城的倉庫就要拆了。所以你很聰明,只把倉庫的所有權轉讓給B城的人。你只需要把倉庫地址告訴他(移動,即交換指針),他自己去取。省去了搬運的體力活!

返回值優化RVO(返回值優化)/NRVO(編譯器優化):編譯器這個“上帝”看到了你的整個計劃。它直接說:“別在A城建倉庫了,我直接在B城給你建好,你一開始就把家具放那里!”它完全消除了“移動”或“拷貝”這個動作本身。這是最極致的效率。

  1. 編譯器的“神來之筆”:返回值優化
    返回值優化是編譯器被標準允許的一種優化,它可以直接在函數外部(調用者的棧幀上)構造本應在函數內部返回的對象,從而完全避免任何拷貝或移動操作。

代碼示例:

BigData createBigData() {BigData data(1000000); // 理論上,這里在函數內部構造`data`// ... 一些對data的操作return data;          // 理論上,這里需要將`data`返回出去
}int main() {BigData my_data = createBigData(); // 理論上,這里需要接收返回的`data`return 0;
}

未優化時的邏輯路徑:

在createBigData函數內部調用構造函數BigData(1000000),創建data。

函數返回時,調用拷貝/移動構造函數,用data構造一個臨時對象。

在main函數中,調用拷貝/移動構造函數,用臨時對象構造my_data。

銷毀臨時對象。

函數結束,銷毀data。

啟用RVO/NRVO后的實際路徑:
編譯器會偷偷重寫你的代碼,變成類似這樣:

void createBigData(BigData& hidden_obj) { // 編譯器偷偷傳進來一個引用hidden_obj.BigData(1000000); // 直接在目標位置構造!// ... 一些對hidden_obj的操作return; // 直接返回,沒有任何拷貝!
}int main() {BigData my_data; // 只分配空間,未初始化createBigData(my_data); // 編譯器偷偷把my_data的引用傳進去構造return 0;
}

你看,my_data其實就是函數里的data,它們根本就是同一個對象! 這就是所謂的“連移動構造都省略了”,因為移動都不需要了。

“通常”這個詞的含義:在現代編譯器中,對于這種簡單的返回局部對象的場景,RVO優化非常強大且幾乎總是會發生(尤其是在Release模式下)。所以你可能在實際運行中看不到移動構造函數被調用。

  1. 程序員的“安全網”:移動語義的保障
    那么問題來了,既然編譯器這么聰明,我們為什么還要費心寫移動語義呢?

因為編譯器不是萬能的上帝,它只能在簡單的、確定的代碼路徑中進行這種優化。一旦代碼變得復雜,優化就可能失敗。

BigData createBigData(bool flag) {BigData data1(1000000);BigData data2(1000000);if (flag) {return data1; // 可能返回這個分支} else {return data2; // 也可能返回那個分支}// 編譯器懵了:我該在調用者那里預先構造data1還是data2?
}int main() {BigData my_data = createBigData(true); // RVO優化可能失敗!return 0;
}
特性RVO(返回值優化)移動語義
實施者編譯器程序員 (通過編寫移動構造函數)
發生階段編譯時運行時
實現原理重新解釋代碼邏輯,直接在目標內存地址構造對象。資源所有權的轉移(例如,交換指針)。
所需條件代碼路徑簡單,符合標準要求。對象必須實現了移動構造函數/賦值運算符。
本質消除“拷貝/移動”這個操作本身將“昂貴的拷貝”操作替換為“廉價的移動”操作。
代碼表現看不見任何調用(構造函數、移動構造都沒有)。能看到移動構造函數被調用。

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

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

相關文章

中小企業SAP B1 HANA部署全解析:成本與云端優勢

目錄 云端部署成本構成與效益分析 軟件許可費 硬件成本 服務費 培訓費 技術優勢 快速部署 彈性擴展 高可用性 云端部署適用場景 IT預算有限的中小企業 分布在不同地區的機構 需要快速上線的情況 本地部署適用場景 數據監管嚴格的行業 擁有完善IT基礎設施企業 …

Django Channels實戰:WebSocket實時通信開發

在當今Web應用開發中&#xff0c;實時通信功能已成為提升用戶體驗的關鍵要素。傳統的HTTP請求-響應模式難以滿足即時聊天、實時通知、協同編輯等場景的需求。本文將深入探討如何利用Django Channels框架實現WebSocket通信&#xff0c;為你的Django項目添加實時交互能力。為什么…

大數據畢業設計選題推薦-基于大數據的懂車帝二手車數據分析系統-Spark-Hadoop-Bigdata

?作者主頁&#xff1a;IT研究室? 個人簡介&#xff1a;曾從事計算機專業培訓教學&#xff0c;擅長Java、Python、微信小程序、Golang、安卓Android等項目實戰。接項目定制開發、代碼講解、答辯教學、文檔編寫、降重等。 ?文末獲取源碼? 精彩專欄推薦??? Java項目 Python…

python 通過selenium調用chrome瀏覽器

更新selenium pip install -U selenium 下載瀏覽器和對應的驅動 Chrome for Testing availability 一般選穩定版本的&#xff0c;我是windows的就下win64的&#xff0c; 下載兩個zip包后&#xff0c;把chromedriver.zip中的exe解壓縮放到chrome_win64文件夾中 from selen…

Codeium:免費開源代碼自動補全工具,高效管理代碼片段告別開發卡殼

你有沒有過這種尷尬時刻&#xff1f;寫代碼時突然想不起來常用的函數寫法&#xff0c;比如 Python 的字典推導式&#xff0c;或者 MySQL 的聯表查詢語句&#xff0c;翻之前的項目文件翻半天&#xff0c;好不容易找到又得復制粘貼 —— 要是遇到換電腦&#xff0c;之前存的代碼片…

嵌入式系統學習Day35(sqlite3數據庫)

一.數據庫 1、分類&#xff1a;大型中型小型 ORACLEMYSQL/MSSQL : SQLITE DBll powdb 關系型數據庫 2、名詞&#xff1a; DB數據庫 select update database DBMS數據庫管理系統 MIS管理信息系統 OA辦公自動化 3、嵌入式數據庫&#xff1a; sqlite3www.sqlite.org www.kernal.…

無人機自組網系統的抗干擾技術分析

由多個無人機和地面組成的MESH自組網系統是一種去中心化的無線通信網絡 。系統由多個機載和地面通信終端構成&#xff0c;其核心特點是“無固定中心”&#xff0c;采用去中心化架構&#xff0c;所有節點地位平等 。在這種網狀結構中&#xff0c;所有通信節點都能直接相互通信&a…

mac 安裝 nginx

安裝 nginx &#xff1a;brew install nginx檢查 nginx 安裝是否成功&#xff1a;nginx -vnginx version: nginx/1.29.1查看 nginx 啟動狀態&#xff1a;sudo brew services info nginx可以看到服務還未啟動nginx (homebrew.mxcl.nginx)Running: ?Loaded: ?Schedulable: ?ng…

JP4-7-MyLesson后臺前端(四)

Java道經 - 項目 - MyLesson - 后臺前端&#xff08;四&#xff09; 傳送門&#xff1a;JP4-7-MyLesson后臺前端&#xff08;一&#xff09; 傳送門&#xff1a;JP4-7-MyLesson后臺前端&#xff08;二&#xff09; 傳送門&#xff1a;JP4-7-MyLesson后臺前端&#xff08;三&am…

Linux control group筆記

Linux CGroup&#xff08;Control Groups&#xff09;是一個強大的內核功能&#xff0c;用于限制、記錄和隔離進程組&#xff08;process groups&#xff09;使用的系統資源&#xff08;如 CPU、內存、磁盤 I/O、網絡等&#xff09;。它通過將進程分組并對這些組進行資源分配和…

小迪Web自用筆記30

Node.js原生態的js運行在前端。Node.js&#xff1a;他與原生態JS最大的不同&#xff0c;就是前端只能看到輸出的代碼&#xff0c;而看不到jS文件req接收&#xff0c;res回顯dirname獲取絕對路徑提交表單 &#xff1a;“Post路由” 到底是什么。這是一個非常核心的Web開發概念。…

并發編程的守護者:信號量與日志策略模式解析

一、信號量 關于信號量的介紹在深入Linux內核&#xff1a;IPC資源管理揭秘 這篇文章當中已經做了初步的介紹了&#xff0c;相信大家對于信號量已經有了初步的認知了。 今天&#xff0c;我們就來探討如何實現信號量。 1. 信號量的接口 //初始化信號量 //成功了&#xff0c;返…

conda 創建環境嵌套報錯

使用conda create --prefix /path可以成功創建&#xff0c;有可能時默認路徑沖突導致的 conda config --show 發現&#xff1a; envs_dirs: /root/autodl-tmp/miniconda3/envs/envs_test/path/root/autodl-tmp/miniconda3/envs/root/.conda/envs 未顯式指定環境路徑&#xf…

低代碼核心原理總結

Web 低代碼平臺核心原理深度解析 1. 架構總覽 Web低代碼平臺的核心架構包含四個關鍵層次&#xff1a; class LowCodePlatform {constructor() {this.visualEditor new VisualEditor(); // 可視化編輯器this.metaDataEngine new MetaDataEngine(); // 元數據引擎this.code…

操作系統研發工作心得體會 - 于復雜性中構建秩序

在操作系統&#xff08;OS&#xff09;研發這片要求極致嚴謹與創新的工程深海中航行數載&#xff0c;我的角色從一個純粹的技術專家&#xff0c;逐漸演變為一個需要兼顧技術深度、系統廣度與團隊效能的復合型角色。這段旅程&#xff0c;讓我深刻體會到&#xff0c;構建一個成功…

Excel 表格 - Excel 減少干擾、專注于內容的查看方式

Excel 減少干擾、專注于內容的查看方式 1、隱藏元素 點擊 【視圖】 -> 取消勾選 【網格線】 -> 取消勾選 【編輯欄】 -> 取消勾選 【標題】2、全屏顯示 點擊 【功能區顯示選項】&#xff08;工具欄右下角小箭頭&#xff09; -> 點擊 【全屏模式】

C# Web API 前端傳入參數時間為Utc

Web API 前端傳入參數時間為Utc&#xff08;時間相差8個小時&#xff09;1.在Program.csbuilder.Services.AddControllers().AddJsonOptions(options > {// 序列化時將時間轉換為本地時間&#xff08;北京時間&#xff09;options.JsonSerializerOptions.Converters.Add(new…

AI Agent開發入門:Semantic Kernel構建智能郵件助手

點擊 “AladdinEdu&#xff0c;同學們用得起的【H卡】算力平臺”&#xff0c;H卡級別算力&#xff0c;80G大顯存&#xff0c;按量計費&#xff0c;靈活彈性&#xff0c;頂級配置&#xff0c;學生更享專屬優惠。 引言&#xff1a;AI Agent——下一代人機交互范式 在人工智能技術…

WebAssembly:開啟高性能 Web 應用的新篇章

在互聯網技術飛速發展的浪潮中&#xff0c;Web應用的性能一直是一個重要的優化目標。傳統的JavaScript雖然靈活便捷&#xff0c;但在處理CPU密集型任務時&#xff0c;其性能瓶頸日益凸顯&#xff0c;限制了Web應用在游戲、音視頻編輯、科學計算、圖像處理等高性能領域的深入發展…

001-003 產品經理-ML應用構建-ML應用范圍

001-003 產品經理-ML應用構建-ML應用范圍 時間&#xff1a;2025年09月08日14:48:01 備注&#xff1a;筆記回顧和復習&#xff0c;僅用于分享而非商用&#xff0c;引用內容若侵權請聯系并刪除。 文章目錄001-003 產品經理-ML應用構建-ML應用范圍導引 學習法則1 內容索引 產品經…