为什么'std::vector<int> b{2};'创建 1 元素向量,而不是 2 元素向量?

Why does 'std::vector<int> b{2};' create a 1-element vector, and not a 2-element one?

本文关键字:元素 向量 创建 lt vector std int gt 为什么      更新时间:2023-10-16

过去几天我一直在玩C++11,我发现了一些奇怪的东西。

如果我想统一初始化一个int:

int a{5};

但如果我对std::vector:做同样的事情

std::vector<int> b{2};

不构造一个双元素数组,而是构造一个包含一个值为2的元素的数组。似乎要获得这种效果,需要更明确地说明:

std::vector<int> c{{2}};
std::vector<int> d = {2};

但与b的声明不同——这似乎不一致。我看过其他一些同样效果的东西。我想问的是,这种行为是在最终的C++11标准中,还是只是在早期实现的草案中?如果是这样的话,为什么标准委员会会将这种行为包括在内?它似乎违背了统一初始化的全部目的,因为必须记住哪些类具有初始值设定项列表构造函数,并对这些类使用旧的()语法而不是{}。或者完全放弃统一初始化。

这似乎是一个很大的"明白"。但它可能有一些我不知道的好处。

编辑:此代码:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> a{2};
    for (auto x: a) {
        std::cout << x << std::endl;
    }
    return 0;
}

在gcc 4.6.2 上输出"2"

是的,根据§13.3.1.7通过列表初始化进行初始化,此行为是有意的

当非聚合类类型T的对象被列表初始化时(8.5.4),过载解析分两个阶段选择构造函数:

--最初,候选函数是初始值设定项列表类T的构造函数(8.5.4),参数列表由初始值设定项列表作为单个参数。

--如果不可行找到初始值设定项列表构造函数,重载解析为再次执行,其中候选函数为类T的构造函数和参数列表由初始值设定项列表的元素。

至于"统一初始化的全部目的"。。。"统一初始化"是一个市场术语,不是一个很好的描述。该标准具有所有常见的初始化形式加上列表初始化,但没有"统一初始化"。列表初始化并不意味着初始化的最终形式,它只是实用工具带中的另一个工具。

统一初始化并不意味着你所认为的那样。添加它是为了使C++中的类型之间的初始化更加统一。理由是:

typedef struct dog_ {
   float height;
   int weight;
} dog;
int main() { 
    dog Spot = { 25.6, 45};
    dog Data[3] = { Spot, {6.5, 7} };
    std::array<dog, 2> data = { { Spot, {6.5, 7} } }; //only in C++ obviously
    return 0;
}

这是有效的C和C++代码,已经存在了很多年。这真的很方便,但你必须记住,这只适用于POD类型。长期以来,人们一直抱怨没有办法实现std::vector<int> data = { 3, 7, 4, 1, 8};,但有些类(std::array)以奇怪的方式编写,允许初始化器列表构造函数。

因此,对于C++11,委员会这样做是为了让vector和其他很酷的类也能做到这一点。这使得所有类型的构造更加统一,因此我们可以使用{}通过构造函数进行初始化,也可以从值列表进行初始化。您遇到的问题是,具有std::initializer_list<int>的构造函数重载是最匹配的,并且将首先被选中。因此,std::vector<int> b{2};并不意味着调用采用int的构造函数,而是意味着从int值的列表中创建vector。在这种情况下,创建一个包含单个值2vector是完全合理的。要调用不同的构造函数,您必须使用()语法,这样C++就知道您不想从列表中初始化。

标准规定初始值设定项列表构造函数优先于其他构造函数。这只是不可能用{}替换()的一种情况。还有其他一些,例如{}初始化不允许缩小转换范围。