在重载的时候,vector会有问题。
当需要可变参数,如果使用vector
的话,可能会遇到下面这个问题。函数f
有两个重载的版本,编译器无法选择具体调用vector
还是list
的版本。
void f(std::vector<int> const &items){};
void f(std::list<int> const &items){};
f({ 1, 2, 3, 4 }); //ambiguous call to overloaded function
而使用initializer_list
的话,就不会出现错误了。编译器优先匹配了initializer_list
的版本。
void g(std::vector<int> const &items){};
void g(std::list<int> const &items){};
void g(std::initializer_list<int> const &items){};
g({ 1, 2, 3, 4 }); // no error
initializer_list不能修改,更符合参数的特点。
vector有push_back函数,也就是说vector可以在函数里面修改,所以必然vector必须在heap上分配空间来存储数据。
而initializer_list
只有begin
和end
函数,函数内并不能修改它,所以编译器有机会在stack上存储initializer_list
的数据来提高性能。
initializer_list has pointer semantics while the vector has value semantics.
vector
是值语义,也就是说拷贝一个vector
,那里面的元素也会被拷贝一次。而initializer_list
是指针语义,里面的元素并不会被拷贝。比如说下面这段代码list
和list2
的begin
其实指向了同一个空间。这样的设计是合理的,因为initializer_list
是不可修改的,没有理由再拷贝一次。
std::initializer_list<int> list = { 1, 2, 3, 4 };
std::initializer_list<int> list2;
list2 = list;
std::cout << list2.begin() << std::endl;
std::cout << list.begin() << std::endl;
指针语义的好处是,下面这段递归函数不会对里面的元素产生很多次复制。虽然每次都构造了一个新的initializer_list
,但是里面的数值{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
并没有经过复制。
int sum(std::initializer_list<int> const &items)
{
std::cout << items.begin() << std::endl;
if (items.begin() == items.end()){
return 0;
}
std::initializer_list<int> next(items.begin() + 1, items.end());
return *(items.begin()) + sum(next);
};
std::cout << sum({ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
initializer_list 背后的设计思想。
在C++11的时候,大家都想加上一个值列表的东西,就像{ value1, value2, value2... valueN }
一样。一种想法是搞出一个新的关键字,给C++增加一个新的build-in类型。但是新的关键字很有可能会导致老的程序无法被编译,如果凑巧老的程序使用了那个关键字做名字。
于是C++11的做法是,只是在标准模板库里面增加一个新的模板initializer_list
,然后让编译器遇到{1,2,3,4}
这种东西的时候,隐式的转换成一个initializer_list
的对象。下面这段代码输出的是class std::initializer_list<int>
auto list = { 1, 2, 3 };
std::cout << typeid(decltype(list)).name() << std::endl;
有了这个基本的东西以后,剩下的问题就可以在已有的框架里面解决了。比如说实现std::vector<int> v = { 1, 2, 3 };
这个功能。其实就是编译器遇到{ 1, 2, 3 }
就生成了一个initializer_list
,然后调用了vector对应的一个构造函数.
vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );
回到最初的f
和g
的例子,g({ 1, 2, 3, 4 });
没有编译错误,因为有一个最佳的匹配。
f({ 1, 2, 3, 4 });
出现了编译错误,因为没有最佳的一个匹配,编译器面临着隐式类型转换,但是有两个选择,vector和list,所以就有编译错误了。