需要可变参数的时候,为什么不用vector代替initializer_list?
2017-02-16 17:21
931 查看
在重载的时候,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,所以就有编译错误了。
相关文章推荐
- 为什么匿名内部类中引用的局部变量和参数需要final而成员字段不用?
- 16 API-集合(List的子类(ArrayList_Vector_LinkedList,集合嵌套遍历),JDK5新特性(泛型,增强for循环,静态导入,可变参数)
- [多问几个为什么]为什么匿名内部类中引用的局部变量和参数需要final而成员字段不用?(转)
- java 集合学习笔记2-ArrayList LinkedList Vector 泛型 增强for 可变参数
- Java基础-16总结List的子类,ArrayList,Vector,LinkedList,泛型,增强for循环,静态导入,可变参数
- 可变参数列表(va_list,va_arg,va_copy,va_start,va_end)
- 使用va_list编写可变参数的函数
- 有关va_list和vsnprintf输出函数的问题(可变参数)
- 关于C中函数的可变参数va_list...(转)
- va_list可变参数传递
- va_list可变参数传递(ZZ)
- 关于C中函数的可变参数va_list...
- 可变参数列表需要注意的问题
- 可变参数函数说明,va_list ,va_start,va_arg,va_end用法
- 可变参数列表的函数调用 va_list va_start va_arg va_end
- 理解可变参数va_list、va_start、va_arg、va_end原理及使用方法
- C++为什么不用delete代替delete[]?
- List和Hashtable都是可以存储数据的,可为什么有时选择List,有时需要Hashtable,这两个有什么区别
- 函数可变参数va_list、va_start、va_arg、va_end原理及使用方法
- 配置PHP安装环境的时候为什么需要安装PHP?