当前位置: 首页 > news >正文

C++中的initializer_list

C++中的initializer_list

  • 型別定義
  • 原始碼
  • typedef
  • 私有成員變數
  • constructor
  • backing array的生命週期
    • 函數g中的支援陣列__a
    • 函數h中的支援陣列__b
  • size
  • begin/end
  • 使用例子
    • 傳遞給函式
    • 用來初始化容器
    • 用於ranged for loop

型別定義

std::initializer_list

std::initializer_listC++ Utilities library std::initializer_list 
(not to be confused with member initializer list)Defined in header <initializer_list>
template< class T >
class initializer_list;
(since C++11)
An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type const T (that may be allocated in read-only memory).A std::initializer_list object is automatically constructed when:a brace-enclosed initializer list is used to list-initialize an object, where the corresponding constructor accepts an std::initializer_list parameter,
a brace-enclosed initializer list is used as the right operand of assignment or as a function call argument, and the corresponding assignment operator/function accepts an std::initializer_list parameter,
a brace-enclosed initializer list is bound to auto, including in a ranged for loop.
std::initializer_list may be implemented as a pair of pointers or pointer and length. Copying a std::initializer_list does not copy the backing array of the corresponding initializer list.The program is ill-formed if an explicit or partial specialization of std::initializer_list is declared.

C++ 公用程式庫中的 std::initializer_list
(注意不要與「成員初始化列表」(member initializer list)混淆)

定義於標頭檔 <initializer_list>

template< class T >
class initializer_list;

(自 C++11 起)

型別為 std::initializer_list<T> 的物件是一個輕量級代理物件(lightweight proxy object),可以用來存取 const T 陣列(這個陣列可能配置在唯讀記憶體中)。

std::initializer_list 物件會在下列情況中自動建構:

  • 情況一:當使用大括號包住的初始化列表(brace-enclosed initializer list)來列表初始化(list-initialize) 物件,且對應的constructor接受 std::initializer_list 參數時。
  • 情況二:當使用大括號包住的初始化列表被當作 賦值運算子的右運算元 或 函式呼叫的引數,且對應的賦值運算子/函式接受 std::initializer_list 參數時。
  • 情況三:當大括號包住的初始化列表被 綁定給 auto(bound to auto)時。ranged for loop也包含在這種情況內。

編譯器可能以「一對指標」或「指標和長度」來實作std::initializer_list
複製一個 std::initializer_list 並不會複製其對應的初始化列表的底層陣列。

如果程式中顯示(完全)或部分特化(explicit or partial specialization)std::initializer_list,則該程式將屬於ill-formed,無法編譯。

原始碼

GCC編譯器對initializer_list的實作位於libstdc++ - initializer_list:

   45   /// initializer_list46   template<class _E>47     class initializer_list48     {49     public:50       typedef _E                value_type;51       typedef const _E&         reference;52       typedef const _E&         const_reference;53       typedef size_t            size_type;54       typedef const _E*         iterator;55       typedef const _E*         const_iterator;56 57     private:58       iterator                  _M_array;59       size_type                 _M_len;60 61       // The compiler can call a private constructor.62       constexpr initializer_list(const_iterator __a, size_type __l)63       : _M_array(__a), _M_len(__l) { }64 65     public:66       constexpr initializer_list() noexcept67       : _M_array(0), _M_len(0) { }68 69       // Number of elements.70       constexpr size_type71       size() const noexcept { return _M_len; }72 73       // First element.74       constexpr const_iterator75       begin() const noexcept { return _M_array; }76 77       // One past the last element.78       constexpr const_iterator79       end() const noexcept { return begin() + size(); }80     };

這段程式碼可分為以下幾個部分:

  • 使用typedef定義成員型別
  • 私有成員變數
  • 兩個constructor
  • size
  • beginend

注意initializer_list並沒有負責元素複製或內存管理的函數。

typedef

std::initializer_list文檔中將各typedef整理如下:

成員型別定義說明
value_typeT元素的型別
referenceconst T&取元素時得到的 reference(const)
const_referenceconst T&同上
size_typestd::size_t元素個數
iteratorconst T*遍歷元素的指標(const)
const_iteratorconst T*同上

私有成員變數

私有成員變數只有:iterator _M_arraysize_type _M_len,所以 initializer_list 其實只是包了一個指向常量陣列的指標跟一個長度值的類別。因為該陣列是常量陣列,所以我們無法修改其內容。

constructor

initializer_list有兩個constructor,其中public版本不接受任何參數;而另一個接受指標和長度的constructor則是private的,只有編譯器可以用。

關於initializer_list的建構,List-initialization (since C++11)提到:

An object of type std::initializer_list<E> is constructed from an initializer list as if the compiler generated and materialized(since C++17) a prvalue of type “array of N const E”, where N is the number of initializer clauses in the initializer list; this is called the initializer list’s backing array.Each element of the backing array is copy-initialized with the corresponding initializer clause of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. A constructor or conversion function selected for the copy is required to be accessible in the context of the initializer list. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.

一個型別為 std::initializer_list<E> 的物件,是從初始化列表(initializer list)建構出來的,就好像編譯器生成並實體化(materialized)(自 C++17 起)一個型別為「大小為 N 的 const E 陣列」的 prvalue,其中 N 是初始化列表中初始化子句(initializer clauses)的數量;這個陣列被稱為該初始化列表的 支援陣列(backing array)

支援陣列中的每個元素,都是由初始化列表中對應的初始化子句進行複製初始化(copy-initialize) 而來的,而 std::initializer_list<E> 物件則被建構來引用該支援陣列
用於複製初始化的建構函式或轉換函式(conversion function)必須在初始化列表的上下文(context)中可被存取。若初始化任何元素時需要做窄化轉換(narrowing conversion),則程式為不合法(ill-formed)。

注1:prvalue包括基本字面值(literals), 返回非引用類型的函數(function calls that return a nonreference type)和臨時物件(temporary objects),詳見Lvalues and Rvalues (C++)

注2:initializer clause即初始化列表中的元素,可以是expression, {}{ initializer-list },參考C++ - Initialization。

注3:根據Copy-initialization,如果初始值是右值(rvalue),則多載決議(overload resolution)會選擇move constructor。即便如此,這仍然算作「複製初始化」(copy-initialization);C++ 中沒有「移動初始化」(move-initialization)這樣的專門術語。

List-initialization (since C++11)中的例子:

void f(std::initializer_list<double> il);void g(float x)
{f({1, x, 3});
}void h()
{f({1, 2, 3});
}struct A { mutable int i; };void q(std::initializer_list<A>);void r()
{q({A{1}, A{2}, A{3}});
}

對於以上的初始化程式,編譯器會以大致等同於以下的方式來實作:

// The initialization above will be implemented in a way roughly equivalent to below,
// assuming that the compiler can construct an initializer_list object with a pair of
// pointers, and with the understanding that `__b` does not outlive the call to `f`.void g(float x)
{const double __a[3] = {double{1}, double{x}, double{3}}; // backing arrayf(std::initializer_list<double>(__a, __a + 3));
}void h()
{static constexpr double __b[3] ={double{1}, double{2}, double{3}}; // backing arrayf(std::initializer_list<double>(__b, __b + 3));
}void r()
{const A __c[3] = {A{1}, A{2}, A{3}}; // backing arrayq(std::initializer_list<A>(__c, __c + 3));
}

f, gh三個函數都可以拆成以下幾個步驟:

  1. 生成一個暫時的常量陣列(backing array)
  2. 假設編譯器能夠用一對指標來建構一個 initializer_list 物件,程式會使用該隱藏建構子建構出 initializer_list:成員變數const E* _M_array會被設為陣列地址,成員變數size_t _M_len被設為陣列長度
  3. initializer_list物件傳入函數fq

以上程式碼展示了backing array的產生 以及 initializer_list 是如何指向它的。

backing array的生命週期

List-initialization (since C++11)中還寫道:

The backing array has the same lifetime as any other temporary object, except that initializing an std::initializer_list object from the backing array extends the lifetime of the array exactly like binding a reference to a temporary.

這個支援陣列的生命週期與其他暫時物件相同,但若用該支援陣列初始化一個 std::initializer_list 物件,則會延長支援陣列的生命週期,就像將一個暫時物件綁定到參考一樣

我們從上面的例子中來看看各backing array的生命週期:

函數g中的支援陣列__a

void g(float x)
{f({1, x, 3});
}

等同於:

void g(float x)
{const double __a[3] = {double{1}, double{x}, double{3}}; // backing arrayf(std::initializer_list<double>(__a, __a + 3));
}

因為std::initializer_list 只是持有指向 __a 的指標,沒有複製陣列,因此必須保證 __a 的生命週期覆蓋 f() 的執行。

關於支援陣列__a,我們可以從程式碼中知道以下幾點:

  • 儲存方式:automatic storage,位於 stack 上
  • 生命週期:從宣告開始,到 g() 函式作用域結束時銷毀,與此同時std::initializer_list 內的指標失效

因為f()函數返回後,g()函數的作用域才會結束,所以以上這段程式碼是安全的。但如果我們把 std::initializer_list 保存起來,在 g() 回傳後使用,就會產生懸垂引用(dangling pointer)的問題。詳見What is a dangling pointer?

注:關於automatic storage和接下來會看到的static storage,詳見Storage Class,Storage class specifiers和Why are the terms “automatic” and “dynamic” preferred over the terms “stack” and “heap” in C++ memory management?。

函數h中的支援陣列__b

void h()
{f({1, 2, 3});
}
void h()
{static constexpr double __b[3] ={double{1}, double{2}, double{3}}; // backing arrayf(std::initializer_list<double>(__b, __b + 3));
}

關於支援陣列__b,我們可以從程式碼中知道以下幾點:

  • 儲存方式:static storage,位於static data region
  • 生命週期:從程式啟動時建立,到程式結束才銷毀

因為__bstatic constexpr,所以跟前一個例子不同, __b 的位址永遠有效,不會懸垂。即便 initializer_list 被傳出去並在函式h外使用,也依然安全,因為 backing array 永遠存在。


範例的注釋中提到:

with the understanding that __b does not outlive the call to f.

翻譯如下:需理解 __b 的生命週期不會比對 f 的呼叫更長。

但是這句話和 static constexpr double __b[3] 的真實生命週期矛盾,因為 static 變數實際上是與程式本身共存亡的。

其實這裡的註解不是在講 b 的實際生命週期,而是說:這個範例只是示意,編譯器生成的 backing array 的生命週期只需要保證能覆蓋 f() 的執行,不需要比它更長。

也就是說對於像 h() 這種情況,編譯器可以選擇把 backing array 做成靜態儲存(就像示例裡的 static constexpr),也就是std::initializer_list中所說的:

that may be allocated in read-only memory

編譯器也可以選擇將backing array放在暫時的區域,只要保證呼叫 f() 的期間它還活著就好。

does not outlive the call to f 這個註解是提醒 programmer不要期待這個 backing array 的位址在 h() 返回後仍然存在(即便實作中可能真的把它放在靜態記憶體內)。

size

回傳成員變數_M_len

begin/end

begin()end()返回的是const_iterator,即const T*

使用例子

傳遞給函式

#include <iostream>void foo(std::initializer_list<int> il) {std::cout << il.size() << std::endl;
}int main() {// 編譯器生成 static const int[3],然後建構 initializer_listfoo({10, 20, 30});return 0;
}

因為函數foo接受initializer_list為參數,符合initializer_list自動建構的情況二,所以此處會由brace-enclosed initializer list自動建構出一個initializer_list後,將它當作參數傳給foo

foo函數中接著調用initializer_listsize函數獲取其長度。

用來初始化容器

#include <iostream>
#include <vector>class Animal {
public:Animal(int f, bool t) : feet(f), tail(t) {}Animal(const Animal& a) : feet(a.feet), tail(a.tail) {std::cout << "copy construct " << a.feet << ", " << a.tail << std::endl;}Animal(Animal&& a) {std::cout << "move construct " << a.feet << ", " << a.tail << std::endl;feet = a.feet;tail = a.tail;a.feet = 0;a.tail = false;}private:int feet;bool tail;
};int main() {Animal a1 = { 4, true };Animal a2 = {2, false};std::vector<Animal> vec_animal = { a1, std::move(a2) };return 0;
}
copy construct 4, 1
move construct 2, 0
copy construct 4, 1
copy construct 2, 0

因為vector有個接受initializer_list的建構子,符合initializer_list自動建構的情況一,所以此處會由brace-enclosed initializer list自動建構出一個initializer_list,再將它當作參數傳給vector的建構子。

從執行結果中可以看到在建構vec_animal時共呼叫了4次constructor。前兩次是由大括號裡的元素構建initializer_list,後兩次則是由intializer_list初始化vector

大括號裡的第二個元素是std::move(a2),因此構建initializer_list時便呼叫了Animal的move consttructor;那麼為何在構建vector時卻沒有用move constructor而用了copy constructor呢?

這其實跟std::initializer_list<T> 的迭代器有關,因為迭代器是const T*型別的,所以vector的建構子在從迭代器中拿元素時只能拿到 const T&型別的物件。

而move constructor只接受非 const 的右值引用T&&(可以被修改)。const T&為常量,不能綁定到需要修改的物件,因此也就無法被轉換成T&&,所以此處才無法調用 move constructor,只能退而求其次地調用copy constructor。

如果想真正 move 進 vector,得避開 initializer_list 這個「陷阱」,改寫成:

std::vector<Animal> v;
v.push_back(std::move(a1));
v.push_back(std::move(a2));

這樣才會直接調用 move constructor。

用於ranged for loop

#include <iostream>int main() {for (int v : {10, 20, 30}) std::cout << v << ' ';return 0;
}

在ranged for loop中使用brace-enclosed initializer list符合initializer_list自動建構的情況三,所以此處會由brace-enclosed initializer list自動建構出一個initializer_list讓for loop來遍歷。

for loop中v的型別是為int,如果換成const int&仍然可以編譯通過,但如果換成int&,就會出現以下編譯錯誤:

main.cpp: In function ‘void foo(std::initializer_list<int>)’:
main.cpp:13:19: error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers13 |     for (int& v : il) std::cout << v << ' ';|                   ^~

這是因為遍歷initializer_list時,獲取到的元素型別為const int &,而const int &型別的變數顯然是無法跟int &綁定的。

http://www.dtcms.com/a/398229.html

相关文章:

  • 关于营销型网站建设的建议促进房地产市场健康发展
  • PHP验证码生成与测试
  • 漫谈<无头浏览器技术>:二、演进之路
  • .NET驾驭Word之力:智能文档处理 - 查找替换与书签操作完全指南
  • 做网站和app哪个难单页网站 jquery
  • 华为od-前端面经-22届非科班
  • 《新能源汽车故障诊断与排除》数字课程资源包开发说明
  • 软件定义汽车---小鹏汽车的智能进化之路
  • 公司做网站需要注意些什么问题wordpress文本框代码
  • SpringMVC 学习指南:从入门到实战
  • 基于 Apache Flink DataStream 的实时信用卡欺诈检测实战
  • 线扫相机的行频计算方法
  • 视频去水印方法总结,如何去除抖音视频水印
  • 中国建设银行青浦支行网站怎样用自己的主机做网站
  • 建设公司网站怎么弄住房和城乡建设部证书查询
  • ensp学习—端口隔离
  • LVS 负载均衡
  • Spring AI 进阶之路03:集成RAG构建高效知识库
  • 【日常学习-理解Langchain】从问题出发,我理解了LangChain为什么必须这么设计
  • 科技的温情——挽救鼠鼠/兔兔的生命
  • 科技赋能噪声防控,守护职业安全健康
  • 一站式平台网站开发技术保定网站建设公司大全
  • 响应式网站自助建站深圳全网推广推荐
  • CodeArts IDE for Cangjie客户端下载与安装
  • Vue 3 —— A / 前置基础知识
  • 百度网站名称及网址网页设计素材代码
  • Apache Hive 能否脱离开Hadoop集群工作
  • 双端 FPS 全景解析:Android 与 iOS 的渲染机制、监控与优化
  • redis之缓存
  • 新网站seo外包蓟县做网站公司