C++ - initializer_list意义、使用
统一初始化
C++11引入一个小概念:为了使 std::initializer_list
工作,引入了一个叫“统一初始化”,又名“大括号初始化 { }”。
#include <iostream>
#include <vector>
#include <array>
int main() {
int i{42};
double d{3.14};
float e{0.114};
char c{'A'};
bool b{true};
std::string s{"Hello, world!"};
std::vector<int> v{1, 2, 3, 4, 5};
std::array<int, 3> a{1, 2, 3};
std::initializer_list<int> il{1, 2, 3};
}
傻春:不是,初始化还写个
{}
,我用个=
不比你这个方便,花里胡哨!
为了杜绝以下现象的发生,C++的统一初始化,就是一个对于隐式转换的一个补丁(((在新语言里面,这种转换多半被禁止,例如kotlin需要手动v.toInt()
!
int i = 3.14;✅
// Clang-Tidy: Narrowing conversion from constant 'double' to 'int'
int j{ 3.14 }; 🛑❌
// error: narrowing conversion of ‘3.1400000000000001e+0’ from ‘double’ to ‘int’ [-Wnarrowing]
大括号初始化,在您编写C++项目的时候,Clion会推荐您使用,甚至补全部分代码。
initializer_list
说回来,std::initializer_list<T>
类型的对象是轻量代理对象,提供对 const T 类型对象数组的访问(可能分配于只读内存)。
#include <cassert>
#include <initializer_list>
#include <iostream>
#include <vector>
template<class T>
struct S
{
std::vector<T> v;
S(std::initializer_list<T> l) : v(l)
{
std::cout << "以包含 " << l.size() << " 个元素的列表构造\n";
}
void append(std::initializer_list<T> l)
{
v.insert(v.end(), l.begin(), l.end());
}
std::pair<const T*, std::size_t> c_arr() const
{
return {&v[0], v.size()}; // 在 return 语句中进行复制列表初始化
// 没有使用 std::initializer_list
}
};
template<typename T>
void templated_fn(T) {}
int main()
{
S<int> s = {1, 2, 3, 4, 5}; // 复制列表初始化
s.append({6, 7, 8}); // 在函数调用中进行列表初始化
std::cout << "现在 vector 含有 " << s.c_arr().second << " 个 int:\n";
for (auto n : s.v)
std::cout << n << ' ';
std::cout << '\n';
std::cout << "用范围 for 遍历花括号初始化式列表:\n";
for (int x : {-1, -2, -3}) // 对 auto 的规则使得此范围 for 有效
std::cout << x << ' ';
std::cout << '\n';
auto al = {10, 11, 12}; // 对 auto 的特殊规则
std::cout << "绑定到 auto 的列表的 size() = " << al.size() << '\n';
auto la = al; // 顶层代理对象的浅层副本
assert(la.begin() == al.begin()); // 保证为真:后备数组相同
std::initializer_list<int> il{-3, -2, -1};
assert(il.begin()[2] == -1); // 注意替代了缺少的 operator[]
il = al; // 浅复制
assert(il.begin() == al.begin()); // 保证为真
// templated_fn({1, 2, 3}); // 编译错误!"{1, 2, 3}" 不是表达式,
// 它没有类型,所以不能推导出 T
templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
templated_fn<std::vector<int>>({1, 2, 3}); // 同样 OK
}
LWG 2129
关于 std::initializer_list
的用户特化。标准中未明确禁止用户对 std::initializer_list
进行特化,这可能导致与标准预期行为不一致。为了解决这个问题,提议在标准中明确说明用户不能显式或部分特化 std::initializer_list
,否则会导致编译错误。
下面是一个错误示例,说明用户试图特化 std::initializer_list
会导致问题:
#include <initializer_list>
// 错误:尝试特化 std::initializer_list
template <>
struct std::initializer_list<int> {
void print() {
// 假设用户自定义了一个打印函数
std::cout << "Custom initializer_list<int>" << std::endl;
}
};
int main() {
std::initializer_list<int> il = {1, 2, 3};
// 如果用户成功特化,编译器将不知道如何处理标准行为
il.print();
return 0;
}