一文速通 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);
相关推荐
闻缺陷则喜何志丹14 分钟前
【计算几何 线性代数】仿射矩阵的秩及行列式
c++·线性代数·数学·矩阵·计算几何·行列式·仿射矩阵得秩
xu_yule27 分钟前
算法基础-背包问题(01背包问题)
数据结构·c++·算法·01背包
特立独行的猫a28 分钟前
C++ Core Guidelines(C++核心准则):2025现代C++开发关键要点总结
c++·core guidelines·核心准测
蒙奇D索大31 分钟前
【数据结构】考研408 | 伪随机探测与双重散列精讲:散列的艺术与均衡之道
数据结构·笔记·学习·考研
嘻嘻嘻开心32 分钟前
List集合接口
java·开发语言·list
Joy-鬼魅35 分钟前
VC中共享内存的命名空间
c++·vc·共享内存命名空间
budingxiaomoli1 小时前
分治算法-快排
数据结构·算法
dragoooon341 小时前
[C++——lesson30.数据结构进阶——「红黑树」]
开发语言·数据结构·c++
云泽8081 小时前
C++ STL 栈与队列完全指南:从容器使用到算法实现
开发语言·c++·算法
历程里程碑2 小时前
C++ 17异常处理:高效捕获与精准修复
java·c语言·开发语言·jvm·c++