用“()”调用构造函数不同于“{}”

Calling constructor with "()" is different from "{}"

为什么这两行代码打印出不同的结果?

std::cout << std::string{6, 's'}
std::cout << std::string(6, 's')

因为std::string have a constructor taking an std::initializer_list, the first example will use that constructor to create a string object with two characters. Initialization like this is called list initialization.

第二个例子将创建一个包含六个字符的字符串对象,全部初始化为's'。这种初始化形式称为 direct initialization.

列表初始化和直接初始化可以是相同的,除了列表初始化禁止从较大类型到较小类型的可能转换,并且如果 class 有一个采用 [= 的构造函数,请注意此处11=].

发生这种情况是因为在第一种情况下,编译器更喜欢另一个重载并使用符号 char(6) 和 's' 初始化字符串。您可以通过将 6 更改为类似 35 的可打印字符来检查它。 尽管有用并解决了最棘手的解析问题,{} 构造也有一些注意事项。

原因是调用的constructors不同

std::string{6, 's'}

此代码使用初始化列表调用构造函数:

basic_string( std::initializer_list<CharT> init, 
    const Allocator& alloc = Allocator() );

因此 6 被转换为 char 并且打印了一个由两个字符组成的字符串。

std::string(6, 's')

此代码调用下一个构造函数:

basic_string( size_type count, 
    CharT ch, 
    const Allocator& alloc = Allocator() );

所以打印了一个由 6 个字符组成的字符串。

{}s 进行的初始化称为列表初始化。列表初始化在几个方面遵循与正常初始化不同的规则,但这里的重要情况与 std::initializer_list.

有关

规则是:如果您正在列表初始化 class 类型 T,并且 class 类型有一个采用 std::initializer_list<U> 的构造函数,并且初始化列表中的每个元素都可以转换为 U,然后该构造函数被 selected。这发生在甚至考虑任何其他构造函数之前,即使该构造函数最终格式错误(例如由于缩小转换)。记住这条规则很重要 - 在这种情况下 std::initializer_list 总是完全优先!


这里 std::string 的两个相关构造函数是(假设 std::string 是一个类型而不是 class 简单的模板特化):

string(std::initializer_list<char> init); // #1
string(size_t count, char ch );           // #2

当您编写 std::string{6, 's'} 时,即列表初始化,因此我们查看是否存在有效的 std::initializer_list 构造函数 - 确实存在! intchar 都可以转换为 char,所以它是 selected。在这种情况下,没有缩小转换,因为 6 适合 char,所以它是 selected 和使用。甚至从未考虑过第二个构造函数。请注意 std::string{300, '.'} 格式错误,因为我们 select std::initializer_list<char> 构造函数但是从 300char 的转换正在缩小。即使其他构造函数 可以工作 ,也没关系,我们选择 std::initializer_list<char> 和错误。

但是当你写 std::string(6, 's') 时,那不是列表初始化。此处考虑了所有构造函数。 std::initializer_list 构造函数不匹配 - 你不能从 int 初始化 std::initializer_list<char> - 但第二个构造函数匹配,所以它是 selected。这是工作中更正常、更熟悉的重载解决方案。


一个好的经验法则是 - {}- 初始化用于初始化聚合(无论如何都没有构造函数)或从特定元素集初始化容器或消除最令人烦恼的解析的歧义。如果您没有做任何这些事情,请使用 ()s。