【C++11】右值引用詳解

文章目錄

  • 前言
  • 1. 左、右值的概念
    • 1.1 左值
    • 1.2 右值
    • 1.3 右值引用
  • 2. 右值引用的價值和使用場景
    • 2.1 左值引用的價值和缺陷
    • 2.2 右值引用的價值和使用場景
    • 2.3 小結
  • 3. 完美轉發
  • 4. 類的移動構造和移動賦值

前言

在C++11之前,面對C++11之前出現的臨時對象的傳參構造,都只有老老實實進行深拷貝一份。但是C++11引入一個新的概念和語法特性(右值和右值引用),進而解決臨時對象的深拷貝問題等等。

本文章小編主要從以下幾個方面來帶讀者認識右值:

  1. 左、右值的概念
  2. 右值引用的價值和應用場景
  3. 完美轉發
  4. 類的移動構造和移動賦值

注:小編的代碼環境VS2022。

1. 左、右值的概念

1.1 左值

  • 左值:

    左值是指具有明確存儲位置(內存地址)的表達式,通常可以出現在賦值語句的左側。左值的特點是持久性,即其生命周期超出表達式求值的范圍。

    兩個特征

    1. 可以取地址。
    2. 可以被賦值。(常變量不可被修改)

例1:

int main()
{int p = 10; //普通整型變量int* a = new int(10); //普通整型指針const int c = 10; //普通常變量"1111111"; //字符串變量return 0;
}

上面代碼中的p,a,c,"1111111"都是左值。它們的共同特點都是:可以被取地址

1.2 右值

  • 右值

    右值是指臨時對象或沒有持久存儲位置的表達式,通常只能出現在賦值語句的右側。右值的特點是短暫性,其生命周期通常僅限于當前表達式。

    兩個特征:

    1. 不能取地址。
    2. 不能出現在表達式左邊。(特例《C++ Primer》中有提到)

例2:

int main()
{int x = 0, y = 1;
// --------------------10;'c';x + y; //表達式計算結果min(x, y); //函數返回結果return 0;
}

類似于上面的10,'c',x + y, min(x, y)這樣的常量或者表達式求值都是屬于右值。它們的特點都是:不能被取地址,即沒有具體的存儲位置!

1.3 右值引用

認識了右值以后,我們來認識右值引用

左值小編已經出過一片文章了:左值引用。這里就不再過多贅訴了。

  • 右值引用:就是對右值取別名。
  • 語法形式:&&。例如:int&& p = 10;這個p就是10的別名。

例3:

int main()
{int a = 10;int& ra = a; //左值引用int&& p1 = 10; //右值引用double x = 1.1, y = 3.3;double&& p2 = x + y; //右值引用return 0;
}

引用都是一樣的,都是為左值或者右值取別名(這里有,我們后面在完美轉發小節的時候會填)

  • 現在我們思考一個問題

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

例4:

#include<iostream>
using namespace std;
int main()
{const int& a = 10; //const左值引用可以引用右值int x = 0;int&& b = std::move(x); //C++標準庫中的move函數可以將一個左值返回一個右值return 0;
}

move的語法詞典

move這個函數可以將左值轉化為右值返回。std::move本質上是一個靜態類型轉換(static_cast),不涉及任何運行時計算或內存操作。其典型實現如下:

template <typename T>
typename std::remove_reference<T>::type&& move(T&& arg) noexcept {return static_cast<typename std::remove_reference<T>::type&&>(arg);
}

模板中的T&&代表萬能引用,即既可以又來引用左值、也可以用來引用右值。

  • 結論:可以左值引用引用右值,右值引用引用左值。

2. 右值引用的價值和使用場景

進入右值引用的價值之前,我們先來回顧一下左值引用的價值。進而找出左值引用的缺陷,然后再引入右值引用的價值

  • 我們先來看一個前置知識。方便后續的理解

例5:

#include<iostream>
using namespace std;
void func(const int& x)
{cout << "void func(const int& x)" << endl;
}void func(int&& x)
{cout << "void func(int&& x)" << endl;
}int main()
{int x = 10;func(10);func(x);return 0;
}

上面的代碼正確嗎?即問題是:左值和右值引用能夠構成重載嗎?

在這里插入圖片描述

顯然:這里的const T&T&&構成函數重載而且沒有調用歧義。在調用的時候,編譯器會調用類型更匹配的函數

  • 前置知識:擁有右值屬性的值會調用右值屬性的參數的函數

2.1 左值引用的價值和缺陷

補充減少拷貝多用于自定義類型中,內置類型拷貝消耗不大。主要考慮自定義類型的拷貝

  • 價值

    1. 做函數參數,減少拷貝。
    2. 做返回值,減少拷貝。
  • 缺陷

    1. 當我們一個函數需要返回一個臨時對象的時候,這個時候我們不能返回一個臨時對象的左值引用!!!

      結果是未知的。

2.2 右值引用的價值和使用場景

我們再看左值引用的使用缺陷:無法返回一個具有臨時性的對象

右值不就是用來引用一個臨時性的對象嗎?

大致我們能夠明白了:右值引用的語法是用來解決左值引用沒有解決的歷史性問題的!!!

接下來,我們詳細探討一個右值引用的價值(自定義類型string為例)

下面是手寫的一個string.h文件,便于我們觀察拷貝的細節。

namespace Er //防止命名沖突
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)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){cout << "string(const string& s) -- 深拷貝" << endl;string tmp(s._str);swap(tmp);}// 賦值重載string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷貝" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做標識的\0};
}
  • 我們先聚焦于這兩個成員函數
    在這里插入圖片描述
    來看下面的場景,例6:
#include"String.h"
#include<iostream>
using namespace std;
Er::string func()
{Er::string str("xxxxx");return str;
}int main()
{Er::string ret;ret = func();return 0;
}

上面代碼中如果沒有編譯器的優化,會進行兩次深拷貝(拋開我們實現的swap之外)這樣的開始是十分恐怖的!原因就在于我們函數返回的參數是一個右值。我們不得不進行深拷貝。

在這里插入圖片描述

對于上面的右值我們有新概念

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

如果我們更想減少拷貝,反正將亡值都快要消失了,那么我們是不是可以將將亡值的成員直接給我交換過來?我的數據和其交換,這樣效率是否會大大提高?是的,這就是移動語義的構造和賦值的主要思想!!!

此時,String.h中添加兩個函數:

// 移動構造
string(string&& s) noexcept:_str(nullptr), _size(0), _capacity(0)
{cout << "string(string&& s) -- 移動語義" << endl;swap(s);
}
// 移動賦值
string& operator=(string&& s) noexcept
{cout << "string& operator=(string&& s) -- 移動語義" << endl;swap(s);return *this;
}

noexcept是表示該函數不會拋異常。swap是自己實現的成員函數Er::string::swap。
由于s是一個將亡值,所以我們直接用this指針指向的內容進行交換,不需要過多的拷貝!!
極大地提高了效率

再來運行上面的例6:

在這里插入圖片描述

對面圖片中提出的這個問題,如果我們需要右值返回,那么我們返回的值是否需要是一個右值呢?是的,但是str并不是一個右值,相反它是一個左值!!

但是為什么調用了移動構造了呢?
先前也有例子,這里再談一遍:因為函數返回不是返回的變量,而是返回的是一個臨時變量!!
函數返回值是一個臨時變量,是一個右值并不是左值

所以總結一下:

  • 使用場景一:右值引用可以用于做函數參數,可用于將亡值對于對象的構造或者賦值。
  • 價值:在接受函數返回值、臨時變量的時候減少深拷貝。

例7:

//頭文件已包含
int main()
{Er::string s; //正常構造Er::string tmp("aaaaa"); //語句一s = "xxxxxx"; //語句二s = std::move(tmp); //右值的移動賦值return 0;
}

來看運行結果:
在這里插入圖片描述

二者都是移動語義

下面進行補充

  • 補充一,關于到底是調用什么拷貝或者賦值函數:

例8:

//頭文件已包含
int main()
{Er::string str1("aaaaa"); //語句一//Er::string str2(move("aaaaa")); //語句二 不要對常量進行moveEr::string tmp = "xxxxx";move(tmp);Er::string str3(tmp); //語句三str1 = "xxxxx"; //語句四return 0;
}
  • 前置:字符串常量是一個左值類型。類型為:const char* const

關于運行結果,小編給出提示,讀者下來自己理解:

  1. 函數重載,參數傳入類型更匹配的地方。解決語句一,語句三。
  2. 隱式類型轉化導致的參數發生變化。解決語句四。
  • 補充二,關于move

例9:
在這里插入圖片描述

  • 我們應該認識到:move的返回值是一個右值引用,并不是將傳入的參數改為右值……

  • 驗證:
    在這里插入圖片描述

  • 使用場景二:作為函數參數,用于一些常用的接口中。

例如STL中C++11各個容器都添加了新的接口:
在這里插入圖片描述
在這里插入圖片描述
……

2.3 小結

右值引用的價值和使用場景:

  • 價值:解決了左值引用沒有解決的臨時對象返回的問題,大大地減少了深拷貝的消耗。

  • 使用場景:

    1. 移動構造和移動賦值
    2. 一些接口的設計

3. 完美轉發

在前面1.3右值引用時提到了T&&代表完美引用。同時也可以解決上面在1.3留的一個坑。

來看下面一個場景:

例10:

//頭文件已經正確包含
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{PerfectForward(10); //右值int a;PerfectForward(a); //左值PerfectForward(std::move(a));//右值const int b = 8;PerfectForward(b); //const 左值PerfectForward(std::move(b));// const 右值return 0;
}
  • 來看運行結果:
    在這里插入圖片描述

    結果全是左值?這是為什么呢???

關于這個問題,我們先回到1.3的例3:

int main()
{int&& p1 = 10; //語句一return 0;
}

對于語句一來說:p1是左值還是右值呢
在這里插入圖片描述

右值引用居然是一個左值???

  • 不管是左值引用還是右值引用,形參的屬性都是左值屬性!!!

如果形參屬性不是左值屬性,那么我們之前代碼所寫的swap(s)這樣的代碼還能成功嗎?這也是編譯器做出的優化,將右值引用的屬性改為左值屬性,這樣更有利于我們的設計。

那么對于這樣的萬能引用,我們應該如何來保持其原有屬性呢?

這就要講到我們所用的完美轉發了:

  • 函數std::forward。forward詞典
    在這里插入圖片描述
  • 作用:在傳參過程中保持對象原生屬性

回到上面的例11:

//其余代碼保持不變
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t)); //即可
}

在這里插入圖片描述

結果完美正確

所以現在再來看STL設計的容器接口,一定是運用到了完美轉發!!!

4. 類的移動構造和移動賦值

  • 針對移動構造函數和移動賦值運算符重載有一些需要注意的點如下:

    1. 如果你沒有自己實現移動構造函數,且沒有實現析構函數拷貝構造拷貝賦值重載中的任意一個那么編譯器會自動生成一個默認移動構造

      默認生成的移動構造函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動構造,如果實現了就調用移動構造,沒有實現就調用拷貝構造。

    2. 如果你沒有自己實現移動賦值重載函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任意一個,那么編譯器會自動生成一個默認移動賦值。

      默認生成的移動賦值重載函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動賦值,如果實現了就調用移動賦值,沒有實現就調用拷貝賦值。(默認移動賦值跟上面移動構造完全類似)

    3. 如果你提供了移動構造或者移動賦值,編譯器不會自動提供拷貝構造和拷貝賦值

簡要概括:

(涉及深拷貝:實現析構函數 、拷貝構造、拷貝賦值重載中的任意一個)、

  1. 不涉及深拷貝的類不需要寫,編譯器自動提供
  2. 涉及深拷貝的類需要寫,編譯器不提供。
  3. 一旦自己寫了,編譯器就不再提供。

完。

  • 小編希望這篇文章能夠幫助你!

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

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

相關文章

如何用自指理解世界

自指即自我指涉&#xff0c;即自己的描述關聯到了自己&#xff0c;典型例子是“這句話是假話”這個悖論。人類對自指的研究由來已久&#xff0c;很多概念、定理都與之相關&#xff0c;由于它的巧妙性&#xff0c;很多學者對其展開了深入研究&#xff0c;并且認為自指是理解宇宙…

Next.js 實戰筆記 2.0:深入 App Router 高階特性與布局解構

Next.js 實戰筆記 2.0&#xff1a;深入 App Router 高階特性與布局解構 上一篇筆記&#xff1a; Next.js 實戰筆記 1.0&#xff1a;架構重構與 App Router 核心機制詳解 上篇筆記主要回顧了一些 Next12 到 Next15 的一些變化&#xff0c;這里繼續學習/復習一些已有或者是新的…

TCP 傳輸時 sk_buff 的 clone 和 unclone

周一有位朋友咨詢個問題&#xff0c;問題本身不重要&#xff0c;但牽扯出的細節卻是非常有趣。 Linux 內核協議棧的 skb 設計非常高效和精巧&#xff0c;多個 skb 可以指向同一塊 data&#xff0c;這就是 clone&#xff0c;當 data 不止一個 skb 指示時&#xff0c;任何一個 s…

【51單片機】51單片機學習筆記-課程簡介

00. 目錄 文章目錄00. 目錄01. 學習哪種類型的單片機02. 學習單片機方法03. 學習單片機硬件設備04. 學習單片機軟件設備05. 學完單片機能做什么06. 附錄01. 學習哪種類型的單片機 單片機的型號那么多&#xff0c;該如何選擇一款合適的進行學習呢&#xff1f;這里給讀者首推的當…

【Docker基礎】Docker端口映射(-p參數)深度解析與實踐指南

目錄 前言 1 Docker網絡基礎 1.1 Docker網絡模型概述 1.2 容器網絡隔離性 2 端口映射基礎 2.1 端口映射概念 2.2 為什么需要端口映射 3 -p參數詳解 3.1 基本語法 3.2 四種映射格式 3.2.1 完整格式 3.2.2 省略宿主機IP 3.2.3 隨機宿主機端口 3.2.4 指定協議類型 …

2、鴻蒙Harmony Next開發:ArkTS語言

目錄 什么是ArkTS&#xff1f; ArkTS的發展趨勢 ArkTS的定位及約束 ArkTS的對UI的拓展 1、UI描述 2、狀態管理&#xff1a; ArkTS語法基礎 基本知識&#xff1a;聲明 基本知識&#xff1a;類型 基本知識&#xff1a;空安全 基本知識&#xff1a;類型安全與類型推斷 …

【Elasticsearch】function_score

如果你希望在 Elasticsearch 查詢中降低某些特定 `id` 的文檔評分,可以通過 `function_score` 查詢結合 `script_score` 函數來實現。`script_score` 允許你使用自定義腳本對文檔的評分進行調整。 以下是一個示例,展示如何降低某些特定 `id` 的文檔評分: 示例場景 假設我們…

vscode打開stm32CubeIDE的項目的注釋問題

文章目錄 目的是為消除紅色底線打開命令面板&#xff1a;CtrlShiftP 搜索并打開&#xff1a;C/C: Edit Configurations (JSON) 修改并添加。&#xff08;注意里面的版本號&#xff09; {"configurations": [{"name": "Win32","includePath&…

ESP32使用freertos更新lvgl控件內容

LVGL不是線程安全&#xff0c;所有 lv_xxx方法只能在GUI主線程調用。 freertos都是線程池&#xff0c;子線程&#xff0c;不能直接更新lvgl&#xff0c;不然看門狗被觸發&#xff0c;死機。 推薦方法案例&#xff1a; 假如搜索wifi列表得到參數是wifi_options&#xff0c;需要通…

OBOO鷗柏丨滿天星(MTSTAR)多媒體信息發布系統技術解析

初次啟動歡迎您使用鷗柏(OBOO)滿天星(MTSTAR)多媒體信息發布系統&#xff0c;在使用本系統的獨立服務器模式前&#xff0c;我們需要完成設備的一些必須設置教程技術說明。其總體流程分為兩步&#xff1a;錄入本地服務器IP地址->連接網絡您獲取到的OBOO鷗柏滿天星(MTSTAR)液晶…

數據結構:棧、隊列、鏈表

目錄 棧 ?隊列 鏈表 棧 棧數據結構特點&#xff1a;先入棧的數據后出&#xff0c;此數據結構常用的方法有&#xff1a;入棧push、出棧pop、查看棧頂元素peek等&#xff0c;下方示例以數組實現棧結構。 package com.ginko.datastructure; import lombok.extern.slf4j.Slf4j…

Python-難點-uinttest

1 需求要求&#xff1a;unittest.TestCase放在列表中&#xff0c;列表存儲的是腳本文件名import使用動態加載方式&#xff1a;importlib.import_module()unittest.TestLoader使用loadTestsFromModule()2 接口3 示例4 參考資料

開源 python 應用 開發(五)python opencv之目標檢測

最近有個項目需要做視覺自動化處理的工具&#xff0c;最后選用的軟件為python&#xff0c;剛好這個機會進行系統學習。短時間學習&#xff0c;需要快速開發&#xff0c;所以記錄要點步驟&#xff0c;防止忘記。 鏈接&#xff1a; 開源 python 應用 開發&#xff08;一&#xf…

ABP VNext + OpenTelemetry + Jaeger:分布式追蹤與調用鏈可視化

ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追蹤與調用鏈可視化 &#x1f680; &#x1f4da; 目錄ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追蹤與調用鏈可視化 &#x1f680;背景與動機 &#x1f31f;環境與依賴 &#x1f4e6;必裝 NuGet 包系統架構概覽…

C語言中整數編碼方式(原碼、反碼、補碼)

在 C 語言中&#xff0c;原碼、反碼、補碼的運算規則與其編碼特性密切相關&#xff0c;核心差異體現在符號位是否參與運算、進位如何處理以及減法是否能轉化為加法等方面。以下是三者的運算規則及特點分析&#xff08;以 8 位整數為例&#xff0c;符號位為最高位&#xff09;&a…

js二維數組如何變為一維數組

在 JavaScript 中&#xff0c;將二維數組轉換為一維數組&#xff08;扁平化&#xff09;有多種方法&#xff0c;可根據數組結構復雜度、性能需求和兼容性選擇。以下是最常用的實現方式&#xff1a; 1. 使用 flat() 方法&#xff08;ES2019&#xff09; MDN釋義&#xff1a;flat…

Claude code在Windows上的配置流程

前言 昨天在服務器上配置好了 Claude code&#xff0c;發現其編碼性能和效率都非常不錯。 然而&#xff0c;嘗試用它修改帶 UI 界面的客戶端程序時頗為不便&#xff0c;因為服務器沒有圖形化界面&#xff0c;無法直接將應用界面直接顯示到開發機上&#xff0c;調試起來頗為不…

手把手教你用YOLOv10打造智能垃圾檢測系統

無需編程基礎&#xff01;手把手教你用YOLOv10打造智能垃圾檢測系統 垃圾分類不再難&#xff0c;AI助手秒識別 你是否曾站在分類垃圾桶前猶豫不決&#xff1f;塑料瓶是可回收還是其他垃圾&#xff1f;外賣餐盒到底該丟哪里&#xff1f;隨著垃圾分類政策推廣&#xff0c;這樣的困…

batchnorm類

1. 偽代碼&#xff1a;2. python代碼&#xff1a;3. 測試&#xff1a;4. 加深理解&#xff1a;以 為例&#xff0c;x3&#xff0c;可見輸出的batchnorm后y0.2627.查看模型記錄的均值及方差&#xff0c;計算y0.286799&#xff0c;理解是大致這樣的計算過程。&#xff08;為什么數…

SpringBoot項目保證接口冪等的五種方法!

1. 冪等概述 1.1 深入理解冪等性 在計算機領域中&#xff0c;冪等&#xff08;Idempotence&#xff09;是指任意一個操作的多次執行總是能獲得相同的結果&#xff0c;不會對系統狀態產生額外影響。在Java后端開發中&#xff0c;冪等性的實現通常通過確保方法或服務調用的結果…