【C++11】移動語義

回顧

const int c的c是可以被取地址的,盡管是常量。所以以是否為常量來判斷是否為右值是錯誤的。

左值與右值正確的區分方法是是否能夠被取地址。(能被取地址也就代表著是一個持久狀態,即有持久的存儲空間的值)

常見的左值有我們定義的變量、對象,或者解引用表達式和傳引用返回。(比如`string s(“2077”);然后s[0]就是一個傳引用返回值,也就是一個左值)

常見的右值有常量、(表達式求值過程中創建的)臨時對象、匿名對象。

左值引用和右值引用可以交叉引用但是有條件:

  • const左值引用可以引用右值

  • 右值引用可以引用move(左值)

    (move是庫里面的一個函數模版,本質內部是進行強制類型轉換,涉及一些引用折疊的知識)

類型是我們對內存里一塊空間的定義,而C++是可以對類型進行轉換的,也就是對內存空間不同的解釋。

類型決定了語法意義上我們怎么對這塊空間的數據進行處理。
拿鏈表指針和迭代器舉個例子:
在這里插入圖片描述
這里ptr和it同樣都是4個字節存放地址,但是卻解釋為不同的類型,意義就不同了、
所以我們知道,內存存的是數據,但只是存儲數據本身沒什么意義,而將數據解釋為不同的類型,意義就千變萬化了。

3.延長生命周期

這一般指的是臨時對象、匿名對象。因為它們一般生命周期只在當前行,而延長后可以與整個域生命周期一樣長。但將其從一個棧幀延長到另一個棧幀,是做不到的。
在這里插入圖片描述
比如這個str的生命周期不可能延長到main棧幀里去
因為延長生命周期沒有改變它的存儲位置
在這里插入圖片描述
在這里,main函數里調用了函數Func1,調用結束后Func1的棧幀是要回收的,下次調用Func2還占用的是這塊空間,所以怎么可能將Func1中的str單獨延長生命周期呢?
所以要搞清楚延長生命周期的對象是指的誰。
延長生命周期沒有改變存儲位置。
再看一例:
在這里插入圖片描述
這里創建一個匿名對象,我們可以看到它在下一句代碼之前就析構了,說明匿名對象的生命周期只在這一行。

那么現在使用const左值引用對其生命周期進行延長:
在這里插入圖片描述

可以看到延長之后它的生命周期就跟著引用走了
在這里插入圖片描述

注意:在這里插入圖片描述
右值引用延長生命周期效果同樣如此。

4.編譯器對拷貝的優化之所以復雜,有兩方面的因素:一方面要支持c++委員會制定的語法新規則,另一方面要為了c++的高效適當進行優化。
左值引用和右值引用最終目的都是減少拷貝提高效率(左值引用還有其他使用場景比如輸出型參數,修改參數或返回值)

(補充)輸出型參數:

在C++中,輸出型參數通常通過指針或引用來實現,因為函數參數默認是按值傳遞的,直接傳遞普通變量無法修改外部變量的值。以下是C++中輸出型參數的實現方式:

1. 使用指針作為輸出型參數

通過傳遞指針,函數可以修改指針所指向的內存地址中的值。

#include <iostream>
using namespace std;// 函數定義,使用指針作為輸出型參數
void calculate(int a, int b, int* sum, int* product) {*sum = a + b;       // 修改sum指向的值*product = a * b;   // 修改product指向的值
}int main() {int x = 5, y = 10;int sumResult, productResult;// 傳遞變量的地址calculate(x, y, &sumResult, &productResult);cout << "Sum: " << sumResult << endl;         // 輸出:Sum: 15cout << "Product: " << productResult << endl; // 輸出:Product: 50return 0;
}

說明:

int* sum 和 int* product 是指針參數,用于接收外部變量的地址。

在函數內部通過 *sum 和 *product 修改外部變量的值。

2. 使用引用作為輸出型參數

引用是C++中更安全和直觀的方式,可以直接操作外部變量。

#include <iostream>
using namespace std;// 函數定義,使用引用作為輸出型參數
void calculate(int a, int b, int& sum, int& product) {sum = a + b;       // 直接修改sum引用的值product = a * b;   // 直接修改product引用的值
}int main() {int x = 5, y = 10;int sumResult, productResult;// 傳遞變量的引用calculate(x, y, sumResult, productResult);cout << "Sum: " << sumResult << endl;         // 輸出:Sum: 15cout << "Product: " << productResult << endl; // 輸出:Product: 50return 0;
}

說明:

int& sum 和 int& product 是引用參數,直接綁定到外部變量。

在函數內部可以直接操作 sum 和 product,無需解引用。

3.指針和引用的對比

在這里插入圖片描述

4. 使用輸出型參數的場景!

  • 需要從函數中返回多個值。

  • 需要修改傳入的參數值。

  • 避免返回大型對象(通過引用或指針傳遞,避免拷貝開銷)。

5. 示例:返回多個值

以下是一個返回多個值的示例,使用引用作為輸出型參數:

#include <iostream>
#include <tuple> // 如果需要返回多個值,也可以使用std::tuple
using namespace std;void getResults(int a, int b, int& sum, int& diff, int& product) {sum = a + b;diff = a - b;product = a * b;
}int main() {int x = 10, y = 4;int sum, diff, product;getResults(x, y, sum, diff, product);cout << "Sum: " << sum << endl;         // 輸出:Sum: 14cout << "Difference: " << diff << endl; // 輸出:Difference: 6cout << "Product: " << product << endl; // 輸出:Product: 40return 0;
}

5.左值引用的不足:
部分函數返回場景,只能傳值返回,不能左值引用返回。
當前函數的局部對象,出了當前函數的作用域生命周期到了銷毀了不能左值引用返回,只能傳值返回。

class Solution 
{
public:// 這?的傳值返回拷?代價就太?了 vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for(int i = 0; i < numRows; ++i){vv[i].resize(i+1, 1);}for(int i = 2; i < numRows; ++i){for(int j = 1; j < i; ++j){vv[i][j] = vv[i-1][j] + vv[i-1][j-1];}}return vv;}
};
int main()
{vector<vector<int>> ret = Solution().generate(100);return 0;
}

不優化的情況下編譯器還要拷貝兩次:
在這里插入圖片描述
如果new的話,忘記釋放可能會導致內存泄漏。

一種較老的比較好的解決方式(輸出型參數):
class Solution 
{
public:void generate(int numRows,vector<vector<int>>& vv) {for(int i = 0; i < numRows; ++i){vv[i].resize(i+1, 1);}for(int i = 2; i < numRows; ++i){for(int j = 1; j < i; ++j){vv[i][j] = vv[i-1][j] + vv[i-1][j-1];}}}
};int main()
{vector<vector<int>> ret;Solution().generate(100,ret);return 0;
}

但這樣寫,用的角度,多多少少很別扭。

因為c++委員會更新標準較晚,編譯器的設計者選擇先從編譯器的角度進行優化。

編譯器的第一輪優化:“跳過中間商”
在這里插入圖片描述
(從左邊這樣到右邊這樣)

namespace bit
{ string addStrings(string num1, string num2){string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());cout << "******************************" << endl;return str;}
}// 場景1 
int main()
{bit::string ret = bit::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}

把優化全關掉的場景:
在這里插入圖片描述
這里前兩組的構造+拷貝構造是"11111"與 "2222"的,最后的兩個拷貝構造一次是str去拷貝構造臨時對象,一次是臨時對象去拷貝構造ret。

VS2019下的優化,合二為一:
在這里插入圖片描述
在這里插入圖片描述

可以看到參數的構造與拷貝構造合二為一了,返回值的兩次拷貝構造也合二為一。
在這里插入圖片描述
2代優化非常恐怖:
在這里插入圖片描述
可以看到是從構造+拷貝構造+拷貝構造到1代的構造+拷貝構造,再到2代的構造

在這里插入圖片描述

在這里插入圖片描述

最后變成了,干脆不創建str了,直接創建ret,讓str變成ret的別名。
這種優化的思路很像上面說的輸出型參數:

vector<vector<int>> ret;
Solution().generate(100,ret);

(編譯器優化后,右值引用沒有意義了嗎?)
答:優化是有限度的,能解決一些問題,但是一些問題也解決不了。

比如在這個場景中:
在這里插入圖片描述
現在不是構造+兩次拷貝構造而是構造+一次拷貝構造+一次拷貝賦值的場景
徹底不優化是這樣的:
在這里插入圖片描述
一代優化:
在這里插入圖片描述
可以看到,傳參是合二為一優化了,但是拷貝構造+拷貝賦值沒有優化
2代優化:
在這里插入圖片描述

2代優化是去掉了拷貝構造,本質是讓構造和拷貝構造合二為一成一次構造了。相當于一上來就構造了臨時對象,讓str是臨時對象的別名。
編譯器優化也是有限度的,優化終止于此了。
C++11出來后這個程序是如何解決的?
如果編譯器徹底不優化,是這樣的:
在這里插入圖片描述
即使這樣這個效率也高,因為移動構造只是搶奪資源,不會拷貝,代價極低。
……
總結就是C++11之前很依賴編譯器的優化,有了移動拷貝和賦值之后,對編譯器的優化的依賴很小,只是錦上添花不再是雪中送炭。
一個問題:每個類在C++11后都要寫移動構造移動賦值嗎?
深拷貝的自定義類型(如string、vector、map…)寫才有價值。
而對于淺拷貝的類型來說,編譯器的優化小賺一筆,所以移動語義+編譯器優化是很無敵的存在。

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

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

相關文章

LangChain教程 - Agent -之 ZERO_SHOT_REACT_DESCRIPTION

在構建智能 AI 助手時&#xff0c;我們希望模型能夠智能地調用工具&#xff0c;以便提供準確的信息。LangChain 提供了 AgentType.ZERO_SHOT_REACT_DESCRIPTION&#xff0c;它結合了 ReAct&#xff08;Reasoning Acting&#xff09;策略&#xff0c;使得 LLM 可以基于工具的描…

移動Android和IOS自動化中常見問題

APP測試邏輯 在app編寫自動化測試用例時&#xff0c;通常會出現只是簡單的點點點過程&#xff0c;然而卻忽略了在實際的自動化實現過程中&#xff0c;軟件是對app元素的判斷來執行測試腳本。所以會出現在后期已經寫好自動化腳本之后還會對測試用例的更新。 App在測試時&#…

python高效試用17---兩個字符串組成一個新的字符串和兩個字符串組成元組作為key哪個更高效

在 Python 中&#xff0c;使用字符串連接 (str1 str2) 作為 key 和使用元組 ((str1, str2)) 作為 key 的效率差異&#xff0c;主要受以下因素影響&#xff1a; 哈希計算速度&#xff1a; 字符串連接 (str1 str2)&#xff1a;會創建一個新的字符串對象&#xff0c;并計算哈希…

深入淺出Java try-with-resources:告別資源泄漏的煩惱

一、為什么需要try-with-resources&#xff1f; 在Java開發中&#xff0c;我們經常需要處理各種資源&#xff1a;文件流、數據庫連接、網絡套接字等。這些資源都有一個共同特點——必須在使用后正確關閉。傳統的資源管理方式存在三大痛點&#xff1a; 代碼臃腫&#xff1a;每…

Python+DeepSeek:開啟AI編程新次元——從自動化到智能創造的實戰指南

文章核心價值 技術熱點:結合全球最流行的編程語言與國產頂尖AI模型實用場景:覆蓋代碼開發/數據分析/辦公自動化等高頻需求流量密碼:揭秘大模型在編程中的創造性應用目錄結構 環境搭建:5分鐘快速接入DeepSeek場景一:AI輔助代碼開發(智能補全+調試)場景二:數據分析超級助…

Linux tcpdump -any抓的包轉換成標準的pcap

在 Linux 中使用 tcpdump -any 抓包并轉換為標準 pcap 文件時出現額外字段,通常與 鏈路層協議頭部的差異 以及 pcap 文件格式的兼容性 有關。以下是詳細原因和解決方案: 一、問題原因分析 -any 選項的局限性 tcpdump -any 會自動猜測鏈路層協議類型(如 Ethernet、IEEE 802…

【SpringMVC】深入解析使用 Postman 在請求中傳遞對象類型、數組類型、參數類型的參數方法和后端參數重命名、及非必傳參數設置的方法

SpringMVC—請求傳參 1. 傳遞對象 如果參數比較多時&#xff0c;方法聲明就需要有很多形參&#xff1b;并且后續每次新增一個參數&#xff0c;也需要修改方法聲明. 我們不妨把這些參數封裝為一個對象&#xff1b; Spring MVC 也可以自動實現對象參數的賦值&#xff0c;比如 Us…

一個差勁的軟件設計

項目概況&#xff1a; 之前自己設計并開發了一個用C#開發的上位機軟件&#xff0c;整個軟件只有一個Form&#xff0c;一個TabControl&#xff0c;3個TabControlPanel&#xff0c;總共100多個lable、textbox、ListBox等控件都放在這3個TabControlPanel里。 問題&#xff1a; 1.…

Linux練級寶典->進程控制詳解(進程替換,fork函數)

目錄 進程創建 fork函數 寫時拷貝 進程終止 進程退出碼 exit函數 _exit函數 return&#xff0c;exit _exit之間的區別和聯系 進程等待 進程等待的必要性 獲取子進程status 進程等待的方法 wait waipid 多子進程創建理解 非阻塞輪詢檢測子進程 進程程序替換 替…

RabbitMq--消息可靠性

12.消息可靠性 1.消息丟失的情況 生產者向消息代理傳遞消息的過程中&#xff0c;消息丟失了消息代理&#xff08; RabbitMQ &#xff09;把消息弄丟了消費者把消息弄丟了 那怎么保證消息的可靠性呢&#xff0c;我們可以從消息丟失的情況入手——從生產者、消息代理&#xff0…

Windows中在VSCode/Cursor上通過CMake或launch文件配置CUDA編程環境

前置步驟 安裝符合GPU型號的CUDA Toolkit 配置好 nvcc 環境變量 安裝 Visual Studio 參考https://blog.csdn.net/Cony_14/article/details/137510909 VSCode 安裝插件 Nsight Visual Studio Code Edition 注意&#xff1a;不是vscode-cudacpp。若兩個插件同時安裝&#xff0c;…

Spark(8)配置Hadoop集群環境-使用腳本命令實現集群文件同步

一.hadoop的運行模式 二.scp命令————基本使用 三.scp命令———拓展使用 四.rsync遠程同步 五.xsync腳本集群之間的同步 一.hadoop的運行模式 hadoop一共有如下三種運行方式&#xff1a; 1. 本地運行。數據存儲在linux本地&#xff0c;測試偶爾用一下。我們上一節課使用…

聚焦兩會:科技與發展并進,賽逸展2025成創新新舞臺

在十四屆全國人大三次會議和全國政協十四屆三次會議期間&#xff0c;代表委員們圍繞多個關鍵議題展開深入討論&#xff0c;為國家未來發展謀篇布局。其中&#xff0c;技術競爭加劇與經濟轉型需求成為兩會焦點&#xff0c;將在首都北京舉辦的2025第七屆亞洲消費電子技術貿易展&a…

【音視頻】ffmpeg命令提取像素格式

1、提取YUV數據 提取yuv數據&#xff0c;并保持分辨率與原視頻一致 使用-pix_fmt或-pixel_format指定yuv格式提取數據&#xff0c;并保持原來的分辨率 ffmpeg -i music.mp4 -t "01:00" -pixel_format yuv420p music.yuv提取成功后&#xff0c;可以使用ffplay指定y…

【從零開始學習計算機科學】計算機體系結構(二)指令級并行(ILP)

【從零開始學習計算機科學】【從零開始學習計算機科學】計算機體系結構(二)指令級并行(ILP) ILP流水線(pipeline)流水線調度循環展開和循環流水循環展開。循環展開的具體步驟可以描述為,軟件流水(循環流水)。我們可以通過流水線的思想處理循環的執行,即不需要這一次的…

android edittext 防止輸入多個小數點或負號

有些英文系統的輸入法,或者定制輸入法。使用xml限制不了輸入多個小數點和多個負號。所以代碼來控制。 一、通過XML設置限制 <EditTextandroid:id="@+id/editTextNumber"android:layout_width="wrap_content"android:layout_height="wrap_conten…

2019年藍橋杯第十屆CC++大學B組真題及代碼

目錄 1A&#xff1a;組隊&#xff08;填空5分_手算&#xff09; 2B&#xff1a;年號字符&#xff08;填空5分_進制&#xff09; 3C&#xff1a;數列求值&#xff08;填空10分_枚舉&#xff09; 4D&#xff1a;數的分解&#xff08;填空10分&#xff09; 5E&#xff1a;迷宮…

從C#中的MemberwiseClone()淺拷貝說起

MemberwiseClone() 是 C# 中的一個方法&#xff0c;用于創建當前對象的淺拷貝&#xff08;shallow copy&#xff09;。它屬于 System.Object 類&#xff0c;因此所有 C# 對象都可以調用該方法。 1. MemberwiseClone() 的含義 淺拷貝&#xff1a;MemberwiseClone() 會創建一個新…

筆記六:單鏈表鏈表介紹與模擬實現

在他一生中&#xff0c;從來沒有人能夠像你們這樣&#xff0c;以他的視角看待這個世界。 ---------《尋找天堂》 目錄 文章目錄 一、什么是鏈表&#xff1f; 二、為什么要使用鏈表&#xff1f; 三、 單鏈表介紹與使用 3.1 單鏈表 3.1.1 創建單鏈表節點 3.1.2 單鏈表的頭插、…

尚硅谷爬蟲note15n

1. 多條管道 多條管道開啟&#xff08;2步&#xff09;&#xff1a; (1)定義管道類 &#xff08;2&#xff09;在settings中開啟管道 在pipelines中&#xff1a; import urllib.request # 多條管道開啟 #(1)定義管道類 #&#xff08;2&#xff09;在setti…