一文速通 std::initializer_list

目录

用途

初始化未显示指定长度的数组,存在语法糖:

cpp 复制代码
int arr[] { 1, 2, 3 };

C++11开始,引入了**"统一初始化"**的概念STL 容器拥有类似的初始化能力,可以使用 **{}**这种通用的语法在任何需要初始化的地方。

原因:STL容器通过使用std::initializer_list 负责接收初始化列表。

cpp 复制代码
vector( std::initializer_list<T> init,
        const Allocator& alloc = Allocator() );
map( std::initializer_list<value_type> init,
     const Compare& comp = Compare(),

     const Allocator& alloc = Allocator() );

大致用法:

cpp 复制代码
#include <initializer_list>

std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector vec = {1, 2, 3, 4, 5}; // CTAD
std::map<std::string, int> m = {
    { "1", 1 }, { "2", 2 }, { "3", 3 }
};
std::set<int> s = { 1, 2, 3 };

当然,可以通过支持initializer_list,来让自定义容器允许"统一初始化":

cpp 复制代码
class FooVector {
    std::vector<int> content_;
public:
    FooVector(std::initializer_list<int> list) {
        for (auto it = list.begin(); it != list.end(); ++it) {
            content_.push_back(*it);
        }
    }
};

FooVector foo_1 = {1, 2, 3, 4, 5};

原理

std::initializer_list 是轻量级的类模板

cpp 复制代码
namespace std {
  template<class E> class initializer_list {
  public:
    using value_type      = E;
    using reference       = const E&;
    using const_reference = const E&;
    using size_type       = size_t;
 
    using iterator        = const E*;
    using const_iterator  = const E*;
 
    constexpr initializer_list() noexcept;
 
    constexpr size_t size() const noexcept;     // number of elements
    constexpr const E* begin() const noexcept;  // first element
    constexpr const E* end() const noexcept;    // one past the last element
  };
 
  // initializer list range access
  template<class E> constexpr const E* begin(initializer_list<E> il) noexcept;
  template<class E> constexpr const E* end(initializer_list<E> il) noexcept;
}
  • 可接收任意长度的初始化列表,但要求元素必须 是/可转换为 同种类型 T。

  • 内部定义了

    • iterator 等容器必需的概念。
    • 成员函数: size()、 begin()、 end()。
cpp 复制代码
std::initializer_list<int> list;
size_t n = list.size(); // n == 0
list = { 1, 2, 3, 4, 5 };
n = list.size(); // n == 5
  • 不负责保存列表中元素的拷贝。看做保存元素的引用,在持有对象的生存期结束之前完成传递。
cpp 复制代码
// 不应该像这样使用:
std::initializer_list<int> func(void) {
    int a = 1, b = 2;
    return { a, b }; // a、 b 在返回时并没有被拷贝
}
// 更好的做法, 构造接收initializer_list作为参数的对象。
//  constexpr vector(initializer_list<T>, const Allocator& = Allocator());
std::vector<int> func(void)
{
    int a = 1, b = 2;
    // vector构造函数接收std::initializer_list作为参数
    return {a, b};
}

加深理解 {} 和 initializer_list

c++中为什么push_back({1,2})可以,emplace_back({1,2})会报错?

明明 vector 存在构造函数,以initializer_list为入参。

cpp 复制代码
  template<class T, class Allocator = allocator<T>>
  class vector {
  public:
	constexpr vector(initializer_list<T>, const Allocator& = Allocator());

为什么push_back({1,2})可以,emplace_back({1,2})会报错?

cpp 复制代码
vector<vector<int>> a;
a.push_back({1,2}); 	// 可以
a.emplace_back({1,2});	// 报错

为什么不可以?

答:{1,2} 本身什么都不是。

{}花括号初始化器列表不是表达式因此它没有类型decltype({1,2})非良构

{1, 2} 变身 std::initializer_list<int>是有条件的:

cpp 复制代码
// 1. 显式声明 initializer_list
std::initializer_list<int> x1 {1, 2};
std::initializer_list x1 {1, 2}; // CTAD模板类型推导


// 2. 用 auto 推导
// 将花括号初始化器列表推导为std::initializer_list
auto x2 = { 1,2,3,4,5,6 };

// braced initialization of a variable declared with a placeholder type but without `=` requires exactly one element inside the bracesC/C++(2663
// auto x2 { 1,2,3,4,5,6 }; // 无法推导

// 3. 函数调用入参为 std::initializer_list
void func(std::initializer_list<int>) {}
func({1, 2, 3});

emplace_back本身是个模板,所以就形成了一个死锁的局面:

  • emplace_back 实例化出接受 std::initializer_list 的版本的前提是 {1,2} 变身。
  • {1,2} 变身的前提是 emplace_back 实例化出接受 std::initializer_list 的版本。
cpp 复制代码
    // modifiers
    template<class... Args>
    constexpr reference emplace_back(Args&&... args);
    constexpr void push_back(const T& x);
    constexpr void push_back(T&& x);

既然这么麻烦,为什么不在编译器开洞,认为{x, x, x} 就是 std::initializer_list<T> 呢?

cpp 复制代码
// 因为这玩意的确还经常不是initializer_list
struct MyStruct {
    int a = 1; int b = 1;
};
MyStruct ms = {1, 2};

class MyClass {
public:
    MyClass(int a, int b);
    // ...
}
MyClass mc {1, 2};

该怎么做

那么如果一定要在 emplace_back 时使用 initializer_list 来简化操作呢?

cpp 复制代码
// 手动构造 initializer_list
a.emplace_back(std::initializer_list<int>{1, 2});

// 放弃emplace_back的自动推导,手动指定emplace_back的模板参数类型
// 函数的参数是initializer_list,{1,2}就会触发变身
a.emplace_back<std::initializer_list<int>>({1, 2});

// 先用得到 initializer_list, 再传入emplace_back
auto x1 = { 1,2,3,4,5,6 }; // 将花括号初始化器列表推导为std::initializer_list
// auto x2 { 1,2,3,4,5,6 }; // 无法推导
a.emplace_back(x1);
相关推荐
GZX墨痕32 分钟前
从零学习直接插入排序
c语言·数据结构·排序算法
Susea&36 分钟前
数据结构初阶:双向链表
c语言·开发语言·数据结构
虾球xz1 小时前
游戏引擎学习第230天
c++·学习·游戏引擎
Net_Walke1 小时前
【C数据结构】 TAILQ双向有尾链表的详解
c语言·数据结构·链表
_x_w1 小时前
【17】数据结构之图及图的存储篇章
数据结构·python·算法·链表·排序算法·图论
冠位观测者2 小时前
【Leetcode 每日一题】2176. 统计数组中相等且可以被整除的数对
数据结构·算法·leetcode
不知道叫什么呀3 小时前
【C语言基础】C++ 中的 `vector` 及其 C 语言实现详解
c语言·开发语言·c++
飞天狗1113 小时前
数据结构——二叉树
数据结构·算法
汇太浪3 小时前
第十六届蓝桥杯大赛软件赛省赛 C++ 大学 B 组 部分题解
c++·蓝桥杯
WW_千谷山4_sch4 小时前
MYOJ_11700(UVA10591)Happy Number(快乐数)(超快解法:图论思想解题)
c++·算法