C++統一初始化語法(列表初始化)

来源:https://www.cnblogs.com/jerry-fuyi/archive/2020/04/30/12806284.html
-Advertisement-
Play Games

引言 要是世上不曾存在C++14和C++17該有多好! 是好東西,但是讓編譯器開發者痛不欲生;新標準庫的確好用,但改語法細節未必是明智之舉,尤其是3年一次的頻繁改動。C++帶了太多歷史包袱,我們都是為之買賬的一員。 我沒那麼多精力考慮C++14/17的問題,所以本文基於C++11標準。 知其所以然, ...


引言

要是世上不曾存在C++14和C++17該有多好!constexpr是好東西,但是讓編譯器開發者痛不欲生;新標準庫的確好用,但改語法細節未必是明智之舉,尤其是3年一次的頻繁改動。C++帶了太多歷史包袱,我們都是為之買賬的一員。

我沒那麼多精力考慮C++14/17的問題,所以本文基於C++11標準。

知其所以然,是學習C++越發複雜的語法的最佳方式。因此,我們從列表初始化的動機講起。

 

動機

早在2005年,Bjarne Stroustrup就提出要統一C++中的初始化語法。這是因為在C++11以前,初始化存在一系列問題,包括:

  • 4種初始化方式:X t1 = v;X t2(v);X t3 = { v };X t4 = X(v);

  • 聚合(aggregate)初始化;

  • defaultexplicit

  • ……

雖然每一個都有辦法解決,但加在一起將會變得非常複雜,對編譯器和開發者都是負擔。換句話說,唯一的需求就是一種統一的初始化語法,其適用範圍能涵蓋先前的各種問題。

於是,列表初始化誕生了。

 

語法

正因為列表初始化是為解決初始化問題而生,列表初始化的適用範圍是任何初始化。你能想到的都寫寫看,寫對就是賺到。

當然,全憑感覺是行不通的,還是得講點道理。列表初始化分為兩類:直接初始化與拷貝初始化。

在直接初始化中,無論構造函數是否explicit,都有可能被調用:

  1. T object { arg1, arg2, ... };,用arg1, arg2, ...構造T類型的對象object——參數可以是一個值,也可以是一個初始化列表,下同;

  2. Class { T member { arg1, arg2, ... }; };,構造member成員對象——花括弧的優勢在這裡體現出來,因為如果是圓括弧的話member會被看作一個函數;

  3. T { arg1, arg2, ... },構造臨時對象;

  4. new T { arg1, arg2, ... },構造heap上的對象;

  5. Class::Class() : member{arg1, arg2, ...} {...,成員初始化列表——除了2以外,其餘都與用()初始化沒有區別。

在拷貝初始化中,無論構造函數是否explicit都會被考慮,但是如果重載決議為一個explicit函數,則此調用錯誤:

  1. T object = {arg1, arg2, ...};,與直接初始化中的1類似,除了explicit以外都相同,operator=不會被調用;

  2. object = { arg1, arg2, ... },賦值語句,調用operator=

  3. Class { T member = { arg1, arg2, ... }; };,與直接初始化中的2類似,explicit同理;

  4. function( { arg1, arg2, ... } ),構造函數參數;

  5. return { arg1, arg2, ... } ;,構造返回值;

  6. object[ { arg1, arg2, ... } ],構造operator[]的參數;

  7. U( { arg1, arg2, ... } ),構造U構造函數的參數。

4~7可以概括為,在該有一個對象的地方,可以用一個列表來構造它。這句話不是很嚴謹,因為除了operator()operator[]以外,其他運算符的參數都不能用列表初始化。

還有一個要註意的地方,是列表初始化不允許窄化轉換(narrowing conversion),即可能丟失信息的轉換,如float轉換為int

#include <iostream>
#include <utility>

struct Test
{
    Test(int, int)
    {
        std::cout << "Test(int, int)" << std::endl;
    }
    explicit Test(int, int, int)
    {
        std::cout << "explicit Test(int, int, int)" << std::endl;
    }
    void operator[](std::pair<int, int>)
    {
        std::cout << "void operator[](std::pair<int, int>)" << std::endl;
    }
    void operator()(std::pair<int, int>)
    {
        std::cout << "void operator()(std::pair<int, int>)" << std::endl;
    }
};

Test test()
{
    return { 1, 2 };
}

int main()
{
    Test t{ 1, 2 };
    Test t1 = { 1, 2 };
    Test t2 = { 1, 2, 3 }; // error
    t[{ 1, 2 }];
    t({ 1, 2 });
}

 

initializer_list

列表不是表達式,更不屬於任何類型,所以decltype({1, 2})是非法的,這還適用於模板參數推導。但是在以下幾種情況中,列表可以轉換成std::initializer_list<T>實例:

  1. 直接初始化中,對應構造函數參數類型為std::initializer_list<T>

  2. 拷貝初始化中,對應參數類型為std::initializer_list<T>

  3. 綁定到auto上(列表元素類型必須嚴格一致),包括範圍for(range for)迴圈——當綁定auto&&時,變數的實際類型為std::initializer_list<T>&&,這是轉發引用的特例。

std::initializer_list是為列表初始化提供的特殊的工具,是一個輕量級的數組代理(proxy),其元素類型為const T。雖然你能在<initializer_list>中看到std::initializer_list類模板的實現,但它實際上是與編譯器內部綁定的,你無法用一個自己寫的相似的類替換它(除非改編譯器)。

std::initializer_list有構造函數、sizebeginend函數,用法與其他STL順序容器類似。迭代器解引用得到const T&類型,元素是不能修改的。

std::initializer_list帶來的最明顯的進步就是STL容器可以用列表來初始化,無需再寫那麼多push_back了。

 

重載決議

struct Test
{
    Test(int, int)
    {
        std::cout << "Test(int, int)" << std::endl;
    }
    Test(std::initializer_list<int>)
    {
        std::cout << "Test(std::initializer_list<int>)" << std::endl;
    }
};

如果我寫Test{1, 2},哪個構造函數會被調用呢?回答這個問題,需要對與列表相關的重載決議有所瞭解。

對於涉及到構造函數的列表初始化(不涉及到的包括聚合初始化等),各構造函數分兩個階段考慮:

  1. 如果有構造函數第一個參數為std::initializer_list,沒有其他參數或其他參數都有預設值,則匹配該構造函數(這裡似乎允許窄化轉換,我測試起來也是如此)——std::initializer_list優先順序高

  2. 否則,所有構造函數參與重載決議,除了窄化轉換不允許,以及拷貝初始化與explicit的衝突依然有效。

所以上面那段程式中Test{1, 2}會匹配第二個構造函數。

如果有多個std::initializer_list重載呢?眾所周知,重載決議中參數轉換有完美、提升、轉換三個等級,std::initializer_list參數的轉換等級定義為所有元素中最差的(不允許窄化轉換),然後找出等級最高的調用,如果有多個則為二義調用。

如果沒有std::initializer_list重載呢?由於從列表到參數本身就是轉換,屬於最差的等級,如果有多個函數可以通過參數轉換後匹配,則該調用就是二義調用;只有當只有一個函數可行時才合法。

 

總結

列表初始化是一種萬能的初始化語法,適用範圍廣導致其規則比較複雜,我們應當結合其動機來理解標準規定的行為。

列表初始化包括直接初始化與拷貝初始化,後者涵蓋了參數與返回值等情形。當我們不想要隱式拷貝初始化時,要用explicit關鍵字來拒絕。

列表不屬於任何類型,但一些情況下可以轉換成std::initializer_list。在重載決議中,std::initializer_list有更高的優先順序。


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

-Advertisement-
Play Games
更多相關文章
  • JavaScript實現一定時長下點擊次數 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale= ...
  • TypeScript 數據類型 //字元串 let str: string = "你好ts" let str1: string = "你好typescript" //布爾 var flag: boolean = true flag = false //數字 var a: number = 123 c ...
  • 0.前言主要說明本次博客針對的作業情況 第二階段的學習也結束了,在此做一些總結和回顧;相比第一階段的學習確實難度增加了,作業的分數也不太好,完成作業需要花的時間更多了。1.作業過程總結①總結三次作業之間的知識迭代關係; 第四次作業中主要是正則表達式和繼承關係的運用,第五次作業就是關於類的繼承和多態性 ...
  • 設計模式總結 創建型模式 創建型模式隱藏了這些類的實例是如何被創建和放在一起,整個系統關於這些對象所知道的是抽象類所定義的介面。這樣,創建型模式在創建了什麼、誰創建它、它是怎麼被創建的,以及何時創建這些方面提供了很大的靈活性。 1. 單例模式 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。 ...
  • 項目概述: 【備註】本項目開發流程,將依次按照上述開發步驟編排,後續將出第一篇.... ...
  • 1.Spring 框架的概念? Spring框架是一個開放源代碼的J2EE應用程式框架,由Rod Johnson發起,是針對bean的生命周期進行管理的輕量級容器(lightweight container)。 Spring解決了開發者在J2EE開發中遇到的許多常見的問題,提供了功能強大IOC、AO ...
  • 一、enum關鍵字 enum關鍵字是在Java1.5也就是Java SE5之後引入的一個新特性:它通過關鍵字enum來定義一個枚舉類,這個被定義的枚舉類繼承Enum類,這個枚舉類算是一種特殊類,它同樣能像其他普通類一樣擁有構造器、方法,也能夠實現介面,但是它不能再繼承其他別的類,因為它的直接父類是E ...
  • 十分鐘腦圖加說明,搞定系統分析師的絕對核心 需求工程 前言 分析師系列,我已經很久沒有寫了。 因為系統分析師的考試內容與系統架構設計師的考試內容有許多重合部分。所以,我在 "系統分析師 軟體水平考試(高級) 開篇" 著重描述了兩者的區別,就沒有花費太多時間在這方面了。 然後最近有好幾位私信我,向我詢 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...