C++11是个分水岭。在这之前,C++标准委员会花了8年时间打磨,最终在2011年8月发布。它最初被叫做C++0x,因为原计划在2010年前推出,结果一拖再拖。C++11之后的节奏就稳了------每3年一个版本,C++14、17、20,规律迭代。
标准更新的动力很朴素:标准化既有实践,并提升抽象能力。比如列表初始化,就是想用一套语法覆盖所有初始化场景,省得你费心记各种形式的区别。
目录
[1. 从C++98的{}到C++11的统一初始化](#1. 从C++98的{}到C++11的统一初始化)
[2. std::initializer_list:让花括号更强大](#2. std::initializer_list:让花括号更强大)
1. 从C++98的{}到C++11的统一初始化
C++98里,花括号只能初始化数组和POD结构体:
cpp
int arr[] = {1, 2, 3, 4, 5};
int arr2[5] = {0};
struct Point { int _x; int _y; };
Point p = {1, 2};
到了C++11,情况变了。一切对象都可以用{}初始化,内置类型、自定义类型、容器,一视同仁。甚至可以把等号省掉:
cpp
int x1 = {2};
int x2{2};
Point p1{1, 2};
const Point& p2 = {1, 2}; // 引用绑定临时对象
class Date {
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year), _month(month), _day(day) { }
private:
int _year, _month, _day;
};
Date d1 = {2025, 1, 1}; // 等价于 Date d1{2025, 1, 1}
const Date& d2 = {2024, 7, 25}; // 临时对象生命周期延长
这种写法背后发生了类型转换:{2025,1,1}会构造一个Date临时对象,再去拷贝给目标。但现代编译器会直接优化成原地构造,拷贝构造被消掉了。你可以自己加打印验证------拷贝构造不会被调用。
使用容器时,这种写法的便利性更明显:
cpp
vector<Date> v;
v.push_back({2025, 1, 1}); // 比push_back(Date(2025,1,1))利落
2. std::initializer_list:让花括号更强大
上面的写法已经挺方便了,但还有一个需求没解决:用一堆值直接初始化容器。
cpp
vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2 = {1, 2, 3, 4, 5, 6, 7};
要支持这种用法,以前得写多个不同参数个数的构造函数。C++11通过std::initializer_list一劳永逸地解决了这个问题。
initializer_list内部可以理解为在栈上分配了一个临时数组,存两个指针(开始和结束),支持迭代器遍历。当你写下{1,2,3}时,编译器会为你构造一个initializer_list<int>对象。
cpp
auto il = {10, 20, 30}; // il 类型为 initializer_list<int>
cout << sizeof(il) << endl; // 典型实现为两个指针大小
STL容器都新增了接受initializer_list的构造函数和赋值运算符:
cpp
vector(initializer_list<value_type> il,
const allocator_type& alloc = allocator_type());
vector& operator=(initializer_list<value_type> il);
// list, map 同理
这样,任意多个值的初始化只需要一份实现:
cpp
template<class T>
class my_vector {
public:
my_vector(initializer_list<T> l) {
for (auto e : l)
push_back(e);
}
// ...
};
于是可以这样写:
cpp
vector<int> v1({1,2,3,4,5});
vector<int> v2 = {1,2,3,4,5};
const vector<int>& v3 = {1,2,3,4,5};
map<string, string> dict = {{"sort", "排序"}, {"string", "字符串"}};
v1 = {10,20,30,40,50}; // initializer_list版本的赋值
值得注意的是,v1({1,2,3,4,5})是直接构造,v2 = {1,2,3,4,5}则是构造临时对象再通过拷贝/移动初始化v2,但编译器通常会优化成直接构造。两者语义上有细微差别,但实际运行效果往往一致。
列表初始化背后,本质是C++11对"统一抽象"的执念。一套语法,覆盖所有初始化场景,降低心智负担,这方向没错。