自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

全面掌握 C++ && 的兩種模式及其應(yīng)用場景

開發(fā) 前端
右值引用&& 是 C++11 的性能優(yōu)化利器,專門處理“一次性用品”,配合 std::move 實現(xiàn)高效的資源轉(zhuǎn)移。萬能引用&& 是泛型編程和完美轉(zhuǎn)發(fā)的核心,它像個變色龍,能適應(yīng)并保持參數(shù)的原始“價值”,配合 std::forward 確保信息無損傳遞。?

今天,來扒一扒 C++ 里容易讓人理解混淆的 && 符號,特別是它在“萬能引用”和“右值引用”這兩個身份間反復(fù)橫跳的騷操作。

第一:右值引用 - "一次性"道具的專屬接收器

在 C++11 這個偉大的版本問世之前,我們 C++ 程序員過著相對"樸素"的生活。

對象要么是"有名有姓"的左值(Lvalue),比如 int a = 10; 里的 a,你可以反復(fù)用它的名字找到它,給它賦值,取它的地址,就像你家養(yǎng)的那只可以擼可以喂、隨時能找到的貓。

要么就是曇花一現(xiàn)的右值(Rvalue),比如 10、a + b 的結(jié)果、函數(shù)返回的臨時對象 getString()。它們就像你在路邊撿到的、用完就可能消失的優(yōu)惠券,或者外賣送的一次性筷子,用完就扔,通常沒有名字,也不能(或者不應(yīng)該)對它們進行修改。

拷貝這些"一次性用品"往往是浪費的。比如你寫 std::string s = getString();,如果 getString() 返回一個臨時的 std::string 對象(右值),老版本的 C++ 會傻乎乎地把這個臨時對象里的數(shù)據(jù)(比如一大段文字)完完整整地復(fù)制一份到新的 s 對象里,然后那個臨時對象就被銷毀了。這就像你點外賣,人家送來一份用精美一次性餐盒裝的飯,你非得把它小心翼翼地倒進你自己的碗里,然后把那個還能用的餐盒扔掉... 何必呢?

于是,C++11 帶來了右值引用,語法就是 類型&&。它的核心使命只有一個:綁定到右值!

void process_disposable(std::string&& disposable_cup){
    // 這里的 disposable_cup 明確表示:我只接收那些“一次性”的 string!
    std::cout << "Processing the disposable cup's content: " << disposable_cup << std::endl;
    // 重點來了:我可以“偷”走它的資源!
    std::string my_permanent_mug = std::move(disposable_cup); // 資源轉(zhuǎn)移,杯子空了
    std::cout << "Content moved to my mug: " << my_permanent_mug << std::endl;
    // 注意:disposable_cup 現(xiàn)在可能為空了,不能再依賴它的內(nèi)容了
}

intmain(){
    std::string permanent_bottle = "Water";
    // process_disposable(permanent_bottle); // 編譯錯誤!人家不要你的“永久水瓶”(左值)
    process_disposable("Juice"); // OK!"Juice" 是個臨時字符串(右值)
    process_disposable(std::string("Milk")); // OK!std::string("Milk") 創(chuàng)建臨時對象(右值)
    
    std::string another_bottle = "Soda";
    // 如果你非要把你的永久水瓶當一次性的給,需要顯式“打包”
    process_disposable(std::move(another_bottle)); // OK!std::move 把它偽裝成右值
    // 但要小心,another_bottle 的內(nèi)容可能被“偷”走了!
    
    return0;
}

生活案例:右值引用就像是"二手閑置物品接收點"

想象一下,你家小區(qū)門口有個牌子寫著:“閑置物品(即將丟棄)接收點,聯(lián)系人:張三 &&”。

  • 規(guī)則:這個接收點(張三&&)只接收那些你明確表示“我不要了,準備扔了”的東西(右值)。比如你剛喝完的一次性飲料瓶、過期的雜志、穿不了的舊衣服。
  • 好處:張三(右值引用)拿到這些東西后,可以“廢物利用”,比如把瓶子拿去賣錢,把雜志內(nèi)容剪下來做手工,把舊衣服拆了做抹布(對應(yīng) C++ 的移動語義 ,轉(zhuǎn)移資源而不是拷貝)。他知道這些東西的原主人不打算再要了,所以可以大膽地“破壞性”使用。
  • 限制:你不能把你家祖?zhèn)鞯?、還在用的電視機(左值 my_tv)直接搬過去給張三,他會拒收,說:“嘿!這玩意兒你還用呢,我不能收!”. 除非你鄭重聲明:“這電視我確實不要了!給你了!”,相當于你對電視機用了 std::move(my_tv),把它“標記”為可以被接收的狀態(tài)。但一旦你這么做了,就別指望回家還能看這臺電視了,它的“靈魂”(資源)可能已經(jīng)被張三搬走了。

總結(jié)一下右值引用:

  • 語法:類型&& (在類型 不是 模板參數(shù)推導(dǎo)上下文,或者 auto&& 推導(dǎo)上下文時)
  • 作用:專門綁定到右值。
  • 目的:實現(xiàn)移動語義,避免不必要的拷貝,提升性能。就像那個只收閑置品的張三,高效利用資源。

第二:萬能引用 - "百變星君"的身份魔法

再說到萬能引用!它是 C++ 界的“百變星君”,它也用 && 符號,但玩法完全不同!

萬能引用,由 Scott Meyers 大神提出,雖然現(xiàn)在官方和很多開發(fā)者更傾向于叫它 轉(zhuǎn)發(fā)引用,但“萬能引用”這個名字實在太形象了,我們先用著,后面再強調(diào)它的核心使命是“轉(zhuǎn)發(fā)”。

萬能引用只在特定的上下文中出現(xiàn),滿足以下兩個條件時,T&& 才不是右值引用,而是萬能引用:

1. 發(fā)生在模板類型推導(dǎo)中:函數(shù)模板的參數(shù)類型是 T&&,其中 T 是需要推導(dǎo)的模板參數(shù)。

template<typename T>
void magic_box(T&& item) { // <--- 這里的 T&& 就是萬能引用!
    // ... 魔法操作 ...
}

2. 發(fā)生在 auto類型推導(dǎo)中:變量聲明使用 auto&&。

auto&& magic_variable = some_expression; // <--- 這里的 auto&& 也是萬能引用!

關(guān)鍵區(qū)別:看到?jīng)]?類型推導(dǎo)!這就是區(qū)分它是“專一的右值引用”還是“百變的萬能引用”的唯一標準!

1、如果 && 所在的類型涉及到編譯器的類型推導(dǎo)(typename T 或 auto),那它就是萬能引用;

2、如果類型是寫死的(比如 std::string&&),那就是右值引用。

那么,“萬能”體現(xiàn)在哪里呢?

萬能引用之所以“萬能”,是因為它既可以綁定到左值,也可以綁定到右值!簡直是通吃!

  • 當你傳遞一個左值給萬能引用時,模板參數(shù) T 會被推導(dǎo)為左值引用類型(例如 int&),然后根據(jù) C++ 的引用折疊規(guī)則,T&& (即 int& &&)會折疊成 int&(左值引用)。
  • 當你傳遞一個右值給萬能引用時,模板參數(shù) T 會被推導(dǎo)為普通類型(例如 int),T&& (即 int&&)就保持為 int&&(右值引用)。
  • 記?。和茖?dǎo)的結(jié)果只有兩個:左值引用或者普通類型,沒有右值引用

轉(zhuǎn)發(fā)引用這套特殊的類型推導(dǎo)規(guī)則總結(jié):

  • 規(guī)則 1:如果傳遞給 T&& 的實參是一個左值 (Lvalue) ,類型為 U,那么 T 會被推導(dǎo)為 U& (左值引用類型)。
  • 規(guī)則 2:如果傳遞給 T&& 的實參是一個右值 (Rvalue) ,類型為 U,那么 T 會被推導(dǎo)為 U (原始非引用類型)。

引用折疊規(guī)則小抄(記住這個,你就掌握了萬能引用的核心秘密):

  • T& & -> T& (左引用 的 左引用 還是 左引用)
  • T& && -> T& (左引用 的 右引用 變成 左引用)
  • T&& & -> T& (右引用 的 左引用 變成 左引用)
  • T&& && -> T&& (右引用 的 右引用 還是 右引用)

簡單記: 

1、只要有 &(左值引用)參與折疊,結(jié)果就是 &(左值引用)。 

2、只有 && 和 &&碰頭,結(jié)果才是 &&(右值引用)。

3、推導(dǎo)是針對 T 進行,引用折疊是針對參數(shù)進行,先進行推導(dǎo),然后拿推導(dǎo)出的 T 對參數(shù)進行引用折疊,得到最后的值

看個例子:

#include <iostream>
#include <string>
#include <utility> // 為了 std::forward

voidprocess_further(const std::string& s){
    std::cout << "Processing as LValue (const ref): " << s << std::endl;
}

voidprocess_further(std::string&& s){
    std::cout << "Processing as RValue (move): " << s << std::endl;
    // 可以在這里移動資源 s
}

template<typename T>
voidmagic_box(T&& item){
    std::cout << "Inside magic_box: ";
    // 僅僅打印類型不夠直觀,我們后面會看怎么用它
    // 關(guān)鍵點:無論傳入的是左值還是右值,item 在 magic_box 函數(shù)內(nèi)部,
    // 因為它有名字了,所以它本身是一個左值!
    // just_print(item); // 如果直接傳遞 item,總是傳遞左值

    // 正確的做法是“完美轉(zhuǎn)發(fā)”!
    process_further(std::forward<T>(item));
}

intmain(){
    std::string lv_string = "I am an LValue";
    magic_box(lv_string); // 傳入左值

    magic_box("I am an RValue"); // 傳入右值 (字符串字面量轉(zhuǎn)臨時 string)
    magic_box(std::string("Another RValue")); // 傳入右值 (臨時 string 對象)

    std::string another_lv = "One more LValue";
    magic_box(std::move(another_lv)); // 傳入被 std::move 轉(zhuǎn)換的右值

    return0;
}

生活案例:萬能引用就像是“萬能快遞代收點”

想象一下,你家小區(qū)新開了一個快遞代收點,招牌是:“快遞代收,聯(lián)系人:李四 <模板 T> &&”。

規(guī)則:

這個李四(T&&)非常靈活,不管是是否保價(保價:左值,普通:右值)的快遞(T),他都能代收。

怎么做到的?

當你送來一個保價包裹(左值 )時,李四心里會記下:“哦,這是個保價物品(T 推導(dǎo)為 Package&),我得按保價物品(Package&)的方式保管。”

當你送來一個普通包裹(右值)時,他記下:“嗯,這是個普通件(T 推導(dǎo)為 Package),按普通件(Package&&)處理就行?!?(這就是類型推導(dǎo) + 引用折疊)

核心價值(即將引出完美轉(zhuǎn)發(fā)):

李四代收了快遞后,他的工作還沒完。他最終要把快遞交給你(或者你指定的下一個人)。這時,他必須 原封不動地 告訴你這個快遞 最初 是個保價件還是普通件。他不能把所有收到的快遞都當成普通件(就像函數(shù)內(nèi)部參數(shù) item 總是左值一樣),也不能都當保價件。他需要一個方法來“恢復(fù)”快遞的原始屬性。

第三:完美轉(zhuǎn)發(fā) - “信使”的神圣使命

我們從上面的 magic_box 例子看到,萬能引用 T&& item 雖然能接收左值和右值,但在函數(shù) magic_box 內(nèi)部,item 這個參數(shù)本身,因為它有了名字,就變成了一個左值!

這就帶來一個問題:如果 magic_box 的目的是要把接收到的 item 原封不動地(保持其原始的左值或右值屬性)傳遞給另一個函數(shù)(比如上面例子中的 process_further),直接傳遞 item 就不行了,因為 item 已經(jīng)是左值了。

這就是 完美轉(zhuǎn)發(fā)(Perfect Forwarding) 的用武之地,而實現(xiàn)它的工具就是 std::forward。

std::forward(item) 的作用就是:根據(jù)模板參數(shù) T 被推導(dǎo)出的原始類型(是 int& 還是 int),將左值 item 轉(zhuǎn)換回它對應(yīng)的原始值類別

1、如果當初傳入 magic_box 的是左值,T 推導(dǎo)為 Type&,std::forward(item) 會返回一個左值引用。

2、如果當初傳入 magic_box 的是右值,T 推導(dǎo)為 Type,std::forward(item) 會返回一個右值引用。

所以,萬能引用的標準用法幾乎總是和 std::forward 成對出現(xiàn),像這樣:

template<typename T>
void forwarding_function(T&& arg) {
    // ... 可能有一些自己的邏輯 ...
    
    // 把 arg 完美轉(zhuǎn)發(fā)給下一個函數(shù)
    callee_function(std::forward<T>(arg)); 
}

生活案例:“萬能快遞代收點”的終極形態(tài)

李四(T&&)的代收點現(xiàn)在升級了:

接收:他能接收任何類型的快遞(萬能引用 T&&),并根據(jù)快遞是保價(左值)還是普通(右值)在小本本上記錄下原始類型(模板推導(dǎo) T 為 Type& 或 Type)。

內(nèi)部處理:在他代收點內(nèi)部,所有快遞暫時都放在一個“已接收”區(qū)域(參數(shù) item 成為左值)。

轉(zhuǎn)發(fā):當他要把快遞交給最終收件人或下一站時,他會查小本本(看 T 的類型),然后使用一個特殊的“轉(zhuǎn)發(fā)標簽”(std::forward),告訴對方:“這個快遞,請按照它原本是保價還是普通的屬性來處理!”(完美轉(zhuǎn)發(fā))。

這樣,無論快遞經(jīng)歷了多少次代收(函數(shù)調(diào)用鏈),只要每一站都使用萬能引用和完美轉(zhuǎn)發(fā),快遞的原始“身份”(左值/右值屬性)就能一直保持下去,直到它被最終消費(比如被移動構(gòu)造或拷貝構(gòu)造)。

第四:總結(jié)與區(qū)分

特性

右值引用 

萬能引用/轉(zhuǎn)發(fā)引用 

語法形式

類型&&

T&& (T 是模板參數(shù)) 或 auto&&

關(guān)鍵條件

類型是確定的,沒有類型推導(dǎo)參與

必須發(fā)生在模板類型推導(dǎo)或auto&&推導(dǎo)上下文中

綁定對象

只能綁定到右值

既能綁定到左值,也能綁定到右值

推導(dǎo)行為

無類型推導(dǎo)

傳入左值時,T推導(dǎo)為Type&;傳入右值時,T推導(dǎo)為Type

引用折疊

不涉及(因為類型固定)

核心機制!Type& && -> Type&Type&& && -> Type&&

主要目的

實現(xiàn)移動語義,優(yōu)化資源轉(zhuǎn)移

實現(xiàn)完美轉(zhuǎn)發(fā),保持值類別在函數(shù)調(diào)用鏈中傳遞

常用搭檔

std::move (用于將左值轉(zhuǎn)為右值以供綁定)

std::forward (用于在函數(shù)內(nèi)部恢復(fù)原始值類別進行轉(zhuǎn)發(fā))

生活類比

閑置物品接收點(只收不要的)

萬能快遞代收點(啥都收,且能保持原始狀態(tài)轉(zhuǎn)發(fā))

如何一眼區(qū)分?

記住這個口訣:

模板推導(dǎo) 或 auto, && 變身萬能佬;類型寫死 不推導(dǎo), &&就是右值寶。

當看到 T&& 或 auto&& 時,問自己:“這里的 T 或 auto 是不是正在被編譯器推導(dǎo)出來?”

如果是,它就是萬能引用。

如果不是,比如 void func(std::string&& s);那它就是“專一”的右值引用。

記?。?/h3>

右值引用&& 是 C++11 的性能優(yōu)化利器,專門處理“一次性用品”,配合 std::move 實現(xiàn)高效的資源轉(zhuǎn)移。

萬能引用&& 是泛型編程和完美轉(zhuǎn)發(fā)的核心,它像個變色龍,能適應(yīng)并保持參數(shù)的原始“價值”,配合 std::forward 確保信息無損傳遞。

責任編輯:武曉燕 來源: CppPlayer
相關(guān)推薦

2019-10-11 07:56:37

物聯(lián)網(wǎng)應(yīng)用物聯(lián)網(wǎng)IOT

2010-02-01 10:22:51

C++數(shù)據(jù)指針

2025-02-10 08:30:00

JavaScrip開發(fā)設(shè)計模式

2015-04-07 10:46:48

Redis

2024-12-19 08:50:38

Redis存儲系統(tǒng)

2024-06-06 08:32:52

.NET框架代碼

2010-01-12 10:57:16

C++的復(fù)雜性

2009-06-29 18:11:40

JSP設(shè)計模式

2021-04-27 08:31:10

前端應(yīng)用場景

2009-07-22 15:50:36

J#和C++ASP.NET

2023-03-15 15:58:11

Python動態(tài)庫C++

2020-07-20 14:00:26

架構(gòu)運維技術(shù)

2023-11-15 18:40:27

半監(jiān)督學習人工智能

2009-08-18 09:22:47

應(yīng)用場景C#分部方法

2014-01-07 14:04:13

HadoopMapReduce

2011-02-23 12:49:31

KonquerorEmbedded

2022-02-21 08:18:38

option編程模式

2010-08-26 15:15:18

DB2備份

2024-03-29 08:33:10

應(yīng)用場景存儲搜索

2012-10-23 09:32:07

點贊
收藏

51CTO技術(shù)棧公眾號