item 24: 區分右值引用和universal引用

来源:http://www.cnblogs.com/boydfd/archive/2016/03/07/5251797.html
-Advertisement-
Play Games

本文翻譯自《effective modern C++》,由於水平有限,故無法保證翻譯完全正確,歡迎指出錯誤。謝謝! 古人曾說事情的真相會讓你覺得很自在,但是在適當的情況下,一個良好的謊言同樣能解放你。這個Item就是這樣一個謊言。但是,因為我們在和軟體打交道,所以讓我們避開“謊言”這個詞,換句話來說


本文翻譯自《effective modern C++》,由於水平有限,故無法保證翻譯完全正確,歡迎指出錯誤。謝謝!

古人曾說事情的真相會讓你覺得很自在,但是在適當的情況下,一個良好的謊言同樣能解放你。這個Item就是這樣一個謊言。但是,因為我們在和軟體打交道,所以讓我們避開“謊言”這個詞,換句話來說:本Item是由“抽象”組成的。

為了聲明一個指向T類型的右值引用,你會寫T&&。因此我們可以“合理”地假設:如果你在源代碼中看到“T&&”,你就看到了一個右值引用。可惜地是,它沒有這麼簡單:

void f(Widget&& param);         // 右值引用

Widget&& var1 = Widget();       // 右值引用

auto&& var2 = var1;             // 不是右值引用

template<typename T>
void f(std::vector<T>&& param); // 右值引用

template<typename T>
void f(T&& param);              // 不是右值引用

事實上,“T&&”有兩個不同的意思。當然,其中一個是右值引用。這樣引用行為就是你所期望的:它們只綁定到右值上去,並且它們的主要職責就是去明確一個對象是可以被move的。

“T&&”的另外一個意思不是左值引用也不是右值引用。這樣的引用看起來像是在源文件中的右值引用(也就是,“T&&”),但是它能表現得像是一個左值引用(也就是“T&”)一樣。它這樣的兩重意義讓它能綁定到左值(就像左值引用)上去,也能綁定到左值(就像左值引用)上去。另外,它能綁定到const或非const對象上去,也能綁定到volatile或非volatile對象上去,甚至能綁定到const加volatile的對象上去。它能綁定到幾乎任何東西上去。這樣空前靈活的引用理應擁有它們自己的名字,我叫它們universal引用(萬能引用)。

universal引用出現在兩種上下文中。最通用的情況是在函數模板參數中,就像來自於上面示例代碼的這個例子一樣:

template<typename T>
void f(T&& param);              // param是一個universal引用

第二個情況是auto聲明,包括上面示例代碼中的這一行代碼:

auto&& var2 = var1;             // var2是一個universal引用

這兩個情況的共同點就是它們都存在類型推導。在模板f中,param的類型正在被推導,並且在var2的聲明式中,var2的類型正在被推導。把它們和下麵的例子(它們不存在類型推導,同樣來自上面的示例代碼)比較一下,可以發現,如果你看到不存在類型推導的“T&&”時,你能把它視為右值引用:

void f(Widget&& param);         // 沒有類型推導
                                // param是右值引用

Widget&& var1 = Widget();       // 沒有類型推導
                                // param是右值引用

因為universal引用是引用,它們必須被初始化。universal引用的初始化決定了它代表一個右值還是一個左值。如果初始化為一個右值,universal引用對應右值引用。如果初始化為一個左值,universal引用對應一個左值引用。對於那些屬於函數參數的universal引用,它在調用的地方被初始化:

template<typename T>
void f(T&& param);              // param是一個universal引用

Widget w;
f(w);                           // 左值被傳給f,param的類型是
                                // Widget&(也就是一個左值引用)

f(std::move(w));                // 右值被傳給f,param的類型是
                                // Widget&&(也就是一個右值引用)

要讓一個引用成為universal引用,類型推導是其必要不補充條件。引用聲明的格式必須同時正確才行,而且格式很嚴格。它必須正好是“T&&”。再看一次這個我們之前在示例代碼中看過的例子:

template<typename T>
void f(std::vector<T>&& param); // param是一個右值引用

當f被調用時,類型T將被推導(除非調用者顯式地指定它,這種邊緣情況我們不關心)。但是param類型推導的格式不是“T&&”,而是“std::vector&&”。按照上面的規則,排除了param成為一個universal引用的可能性。因此param是一個右值引用,有時候你的編譯器會很高興地為你確認你是否傳入了一個左值給f:

std::vector<int> v;
f(v);                           // 錯誤!不能綁定一個左值到右值
                                // 引用上去

甚至一個簡單的const屬性的出場就足以取消引用成為universal的資格:

template<typename T>
void f(const T&& param);        // param是一個右值引用

如果你在一個模板中,並且你看到一個“T&&”類型的函數參數,你可能覺得你能假設它是一個universal引用。但是你不能,因為在模板中不能保證類型推導的存在。考慮一下std::vector中的這個push_back成員函數:

template<class T, class Allocator = allocator<T>>       //來自c++標準庫
class vector {
public:
    void push_back(T&& x);
    ...
};

push_back的參數完全符合universal引用的格式,但是在這個情況中沒有類型推導發生。因為push_back不能存在於vector的特定實例之外,並且實例的類型就完全能決定push_back的聲明類型了。也就是說

    std::vector<Widget> v;

使得std::vector模板被實例化為下麵這樣:

class vector<Widget, allocator<Widget>> {
public:
    void push_back(Widget&& x);     //右值引用
    ...
};

現在你能清楚地發現push_back沒有用到類型推導。vector的這個push_back(vector中有兩個push_back函數)總是聲明一個類型是rvalue-reference-to-T(指向T的右值引用)的參數。

不同的是,std::vector中和push_back概念上相似的emplace_back成員函數用到了類型推導:

template<class T, class Allocator = allocator<T>>
class vector {
public:
    template <class... Args>
    void emplace_back(Args&&... args);
    ...
};

在這裡,類型參數Args獨立於vector的類型參數T,所以每次emplace_back被調用的時候,Args必須被推導。(好吧,Args事實上是一個參數包,不是一個類型參數,但是為了討論的目的,我們能把它視為一個類型參數。)

事實上emplace_back的類型參數被命名為Args(不是T),但是它仍然是一個universal引用,之前我說universal引用的格式必須是“T&&”。在這裡重申一下,我沒要求你必須使用名字T。舉個例子。下麵的模板使用一個universal引用,因為格式(“type&&”)是正確的,並且param的類型將被推導(再說一次,除了調用者顯式指定類型的邊緣情況):

    template<typename MyTemplateType>       // param是一個
    void someFunc(MyTemplateType&& param);  // universal引用

我之前說過auto變數也能是universal引用。更加精確一些,用auto&&的格式被推導的變數是universal引用,因為類型推導有發生,並且它有正確的格式(“T&&”)。auto universal引用不像用於函數模板參數的universal引用那麼常見,但是他們有時候會在C++11中突然出現。他們在C++14中出現的頻率更高,因為C++14的lambda表達式可以聲明auto&&參數。舉個例子,如果你想要寫一個C++14的lambda來記錄任意函數調用花費的時間,你能這麼做:

auto timeFuncInvocation =
    [](auto&& func, auto&&... param)
    {
        start timer;
        std::forward<decltype(func)>(func){             // 用params
            std::forward<decltype(params)>(params)...   // 調用func
        };
        停止timer並記錄逝去的時間。
    };

如果你對lambda中“std::forward”(譯註:blah blah blah發音為:布拉布拉布拉)的代碼感到困惑,這可能只是意味著你還沒讀過Item 33.不要擔心這件事。在本Item中,重要的事情是lambda表達式中聲明的auto&&參數。func是一個universal引用,它能被綁定到任何調用的對象上去,不管是左值還是右值。params(譯註:原文為args,應該是筆誤)是0個或多個universal引用(也就是一個universal引用包),它能被綁定到任何數量的任意類型的對象上去。結果就是,由於auto universal引用的存在,timeFuncInvocation能給絕大多數函數的執行進行計時。(對於為什麼是絕大多數而不是任意,請看Item 30。)

把這件事記在心裡:我們這整個Item(universal引用的基礎)都是一個謊言...額,一個“抽象”!潛在的事實被稱為引用摺疊,這個話題會在Item 28中專門討論。但是事實並不會讓抽象失效。區分右值引用和universal引用將幫助你更精確地閱讀源代碼(“我看到的T&&只能綁定到右值上,還是能綁定到所有東西上呢?”),並且在你和同事討論的時候,它能讓你避免歧義。(“我在這裡使用一個universal引用,不是一個右值引用...”)。它也能讓你搞懂Item 25和Item 26的意思,這兩個Item都依賴於這兩個引用的區別。所以,擁抱抽象並陶醉於此吧。就像牛頓的運動定律(學術上來說是錯誤的)一樣,比起愛因斯坦的相對論(“事實”)來說它通常一樣好用並且更簡單,universal引用的概念也是這樣,比起工作在引用摺疊的細節來說,它是更好的選擇。

            你要記住的事
  • 如果一個函數模板參數有T&&的格式,並且會被推導,或者一個對象使用auto&&來聲明,那麼參數或對象就是一個universal引用。
  • 如果類型推導的格式不是準確的type&&,或者如果類型推導沒有發生,type&&就是一個右值引用。
  • 如果用右值來初始化,universal引用相當於右值引用。如果用左值來初始化,則相當於左值引用。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在C++的類中,都會有一個或多個構造函數、一個析構函數、一個賦值運算操作符。即使我們自己定義的類中,沒有顯示定義它們,編譯器也會聲明一個預設構造函數、一個析構函數和一個賦值運算操作符。例如: 1 //聲明一個空類 2 class Empty{}; 3 4 //但是這個空類和下麵這個類是等同的 5 c
  • SEVERE: The required Server component failed to start so Tomcat is unable to start. org.apache.catalina.LifecycleException: Failed to start component
  • 觀察者模式介紹 觀察者模式是一種非常有用的設計模式,在軟體系統中,當一個對象的行為依賴於另一個對象的狀態時,觀察者模式就非常有用。如果不適用觀察者模式,而實現類似的功能,可能就需要另外啟動一個線程不停地監聽另一個對象的狀態,這樣會得不償失。如果在一個複雜的系統中,可能就需要開啟很多的線程來監聽對象狀
  • String類的三個內建正則表達式工具: 1.matches()方法 示例:檢查一個句子是否以大寫字母開頭,以句號結尾 1 public static boolean checkFormat(String sentence){ 2 return sentence.matches("^[A-Z].+\
  • 本章主要是講解讀者在進行FPGA邏輯設計之前的準備工作,需要下載Quartus II軟體和 Modelsim 軟體,一個是用來進行FPGA邏輯設計,一個是用來對邏輯進行理論分析與驗證。 1.1 quartus 軟體安裝 現在Quartus II軟體已經更新到了15.0版本,這個最新版本的一些特性如下
  • 1. 用“==”比較兩個變數,如果兩個變數是基本類型變數,且都是數值類,則值相等就返回true 如果兩個變數是引用型變數,則兩個對象的地址一樣,即指向同一個對象,則返回true 2.equals:String類對equals進行了重寫:1)若是同一個對象,返回true; 2)若不是,則比較它們的值,
  • 平常閱讀源碼什麼的沒有目的性,所以很少去看什麼源碼,主要是比較繞看起來吃力,所以一般工作只是找個模版模仿一下。 以上廢話,割———————————————————————————————————————————————————————————— 最近照常模仿使用了其它項目里的DataSource用法
  • 一、自動化類型轉換:在某種條件下,系統自動完成類型轉換也稱為隱含轉換 ① 兩種類型相容 ② 目標類型大於源類型 ③ 對於表達式,如果一個操作數為double型,則整個表達式可提升為double型 ④ 示例:int i=10;double d=i; 1 public class DataDemo2 2
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...