调用初始化列表构造函数的不同方式

Different ways of calling an initializer-list-constructor

本文关键字:方式 构造函数 初始化 列表 调用      更新时间:2023-10-16

考虑以下使用初始化列表构造函数的示例:

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的构造函数混淆。而且第一种风格看起来与其他编程语言很相似。

对于string s的vector,三种形式之间没有区别。但是,如果取initializer_list的构造函数是explicit,则第一个和其他两个之间可能存在差异。在这种情况下,第一个copy-list-initialization是不允许的,而另外两个direct-list-initialization是允许的。

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


正如Yakk在注释中指出的那样,当构造的类型没有接受initializer_list的构造函数时,进一步的差异就出现了。

例如,正在构造的类型有一个构造函数,它接受3个参数,所有类型都是char const *,而不是initializer_list构造函数。在这种情况下,表格1 &3是有效的,但2是错误的,因为大括号初始化列表不能匹配包含3个参数的构造函数。

如果类型确实有初始化列表构造函数,但带括号的初始化列表的元素不能隐式转换为initializer_list<T>,则将考虑使用其他构造函数。假设存在另一个匹配的构造函数,则form 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按预期工作,将是包含3个字符串的vector,而v2导致未定义的行为。