初始化时的统一或直接初始化?

时间:2015-01-12 02:42:07

标签: c++ c++11 initialization

假设我有一个存储T类型对象的模板。我想传递构造函数参数以初始化数据成员。我应该使用非花括号的统一初始化或直接初始化吗?:

template<typename T>
struct X
{
    template<typename... Args>
    X(Args&&... args)
             : t(std::forward<Args>(args)...) // ?
    /* or */ : t{std::forward<Args>(args)...} // ?
private:
    T t;
};

如果我要存储的对象是std::vector并且我选择了花括号样式(统一初始化),那么我传递的参数将被转发到vector::vector(std::initializer_list<T>)构造函数,这可能是可能不是我想要的。

另一方面,如果我使用非大括号样式,我将失去通过其std::initializer_list构造函数向向量添加元素的能力。

当我不知道我存储的对象和将传入的参数时,我应该使用什么形式的初始化?

2 个答案:

答案 0 :(得分:4)

要清楚的是,对于具有多个构造函数的类型会产生歧义,其中一个构造函数采用std::initializer_list,另一个构造函数的参数(使用大括号初始化时)可能被编译器解释为std::initializer_list。例如,std::vector<int>

就是这种情况
template<typename T>
struct X1
{
    template<typename... Args>
    X1(Args&&... args)
             : t(std::forward<Args>(args)...) {}

    T t;
};

template<typename T>
struct X2
{
    template<typename... Args>
    X2(Args&&... args)
     : t{std::forward<Args>(args)...} {}

    T t;
};

int main() {
    auto x1 = X1<std::vector<int>> { 42, 2 };
    auto x2 = X2<std::vector<int>> { 42, 2 };

    std::cout << "size of X1.t : " << x1.t.size()
              << "\nsize of X2.t : " << x2.t.size();
}

(请注意,唯一的区别是X2成员初始值设定项列表中的大括号,而不是X1成员初始化列表中的括号

  

输出

     

大小为X1.t:42

     

大小为X2.t:2

Demo


标准库作者在编写std::make_uniquestd::make_sharedstd::optional<>等实用程序模板时遇到了这个实际问题(对于任何类型都应该完美转发):初始化形式是首选?这取决于客户端代码。

没有好的答案,他们通常使用括号(理想情况下记录选择,因此调用者知道会发生什么)。习惯性的现代c ++ 11更喜欢在任何地方进行支持初始化(它避免缩小转换,避免c ++最令人烦恼的解析等等。)


消除歧义的潜在解决方法是使用命名标记,在Andrzej's C++ blog的这篇精彩文章中进行了广泛讨论:

namespace std{
  constexpr struct with_size_t{} with_size{};
  constexpr struct with_value_t{} with_value{};
  constexpr struct with_capacity_t{} with_capacity{};
}

// These contructors do not exist.
std::vector<int> v1(std::with_size, 10, std::with_value, 6);
std::vector<int> v2{std::with_size, 10, std::with_value, 6};

这是冗长的,只有在你可以修改歧义类型时才适用(例如,暴露构造函数采用std::initializer_list的类型和其参数列表可能转换为std::initializer list的其他构造函数)< / p>

答案 1 :(得分:0)

与任何初始化一样,

  • 当对象包含值或多个逐段初始化的值时,请使用大括号。这包括聚合类和数字。

    大括号看起来更像是一个项目列表。

  • 当从参数计算对象的初始状态时使用括号。

    括号看起来更像是一系列函数参数。

此一般规则包括像std::vector<int>这样的容器的情况,可以使用N个数字(std::vector<int>(4,5))或一对数字(std::vector<int>{4,5})进行初始化。

顺便说一句,因为&#34;制服&#34;初始化并不是一个全能的,这个术语是不鼓励的。官方名称是大括号初始化