9

考虑这个初始化器列表构造器使用的例子:

std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
std::vector<std::string> v({ "xyzzy", "plugh", "abracadabra" });
std::vector<std::string> v{ "xyzzy", "plugh", "abracadabra" }; 

它们之间是否有任何差异(甚至是细微的差异)?

在一个大型项目中,您必须定义一个标准,您会选择哪种风格?
我更喜欢第一种风格,第三种风格很容易与使用 args 调用构造函数混淆。第一种风格对其他编程语言来说也很熟悉。

4

1 回答 1

7

在 a vectorof strings 的情况下,三种形式之间没有区别。initializer_list但是,如果构造函数采用is ,则第一个和其他两个之间可能会有所不同explicit。在这种情况下,第一个是copy-list-initialization是不允许的,而另外两个是direct-list-initialization是允许的。

由于这个原因,我更喜欢第三种形式。我会避免使用第二个,因为括号是多余的。


正如 Yakk 在评论中指出的那样,当正在构造的类型没有构造函数采用initializer_list.

比如说,正在构造的类型有一个构造函数,它接受 3 个参数,都是 type char const *,而不是initializer_list构造函数。在这种情况下,形式 1 和 3 是有效的,但 2 是非良构的,因为括号中的括号初始化列表不能匹配 3 参数构造函数。

如果该类型确实具有初始化列表构造函数,但花括号初始化列表的元素不能隐式转换为initializer_list<T>,则将考虑其他构造函数。假设存在另一个匹配的构造函数,则表单 2 会导致构造一个中间副本,而其他两个则没有。这可以通过以下示例来演示,使用-fno-elide-constructors.

struct foo
{
    foo(int, int) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    foo(foo const&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    foo(std::initializer_list<std::string>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

int main()
{
    foo f1 = {1,2};
    std::cout << "----\n";
    foo f2({1,2});
    std::cout << "----\n";
    foo f3{1,2};
}

输出:

foo::foo(int, int)
----
foo::foo(int, int)
foo::foo(const foo&)
----
foo::foo(int, int)


以下案例不是问题的一部分,但仍然需要注意。在某些情况下,使用嵌套大括号会导致不直观的行为。考虑

std::vector<std::string> v1{{ "xyzzy", "plugh", "abracadabra" }};
std::vector<std::string> v2{{ "xyzzy", "plugh"}};

v1按预期工作,将vector包含 3 个字符串,同时v2导致未定义的行为。有关详细说明,请参阅此答案

于 2014-08-04T14:48:02.847 回答