美文网首页
需要可变参数的时候,为什么不用vector代替initializ

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

作者: Roland | 来源:发表于2015-06-22 10:55 被阅读0次

    在重载的时候,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,所以就有编译错误了。

    相关文章

      网友评论

          本文标题:需要可变参数的时候,为什么不用vector代替initializ

          本文链接:https://www.haomeiwen.com/subject/rlgsqttx.html