欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 旅游 > 一文速通 std::initializer_list

一文速通 std::initializer_list

2025/3/17 7:22:03 来源:https://blog.csdn.net/weixin_41733034/article/details/145949340  浏览:    关键词:一文速通 std::initializer_list

目录

  • 用途
  • 原理
  • 加深理解 {} 和 initializer_list
    • 为什么不可以?
    • 该怎么做

用途

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

int arr[] { 1, 2, 3 };

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

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

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() );

大致用法:

#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,来让自定义容器允许“统一初始化”:

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 是轻量级的类模板

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 elementsconstexpr const E* begin() const noexcept;  // first elementconstexpr const E* end() const noexcept;    // one past the last element};// initializer list range accesstemplate<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()。
std::initializer_list<int> list;
size_t n = list.size(); // n == 0
list = { 1, 2, 3, 4, 5 };
n = list.size(); // n == 5
  • 不负责保存列表中元素的拷贝。看做保存元素的引用,在持有对象的生存期结束之前完成传递。
// 不应该像这样使用:
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为入参。

  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})会报错?

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>是有条件的:

// 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 的版本。
    // modifierstemplate<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> 呢?

// 因为这玩意的确还经常不是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 来简化操作呢?

// 手动构造 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);

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词