需要可变参数的时候,为什么不用vector代替initializer_list?

在重载的时候,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只有beginend函数,函数内并不能修改它,所以编译器有机会在stack上存储initializer_list的数据来提高性能。

initializer_list has pointer semantics while the vector has value semantics.

vector语义,也就是说拷贝一个vector,那里面的元素也会被拷贝一次。而initializer_list指针语义,里面的元素并不会被拷贝。比如说下面这段代码listlist2begin其实指向了同一个空间。这样的设计是合理的,因为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() );

回到最初的fg的例子,g({ 1, 2, 3, 4 });没有编译错误,因为有一个最佳的匹配。
f({ 1, 2, 3, 4 });出现了编译错误,因为没有最佳的一个匹配,编译器面临着隐式类型转换,但是有两个选择,vector和list,所以就有编译错误了。


展开阅读全文

没有更多推荐了,返回首页