您的位置:首页 > 其它

需要可变参数的时候,为什么不用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,所以就有编译错误了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  initializer_list vector