初始化时统一初始化还是直接初始化?

问题描述 投票:0回答:3

假设我有一个模板,用于存储

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
构造函数向向量添加元素。

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

c++ c++11 initialization
3个回答
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

演示


标准库作者在编写诸如

std::make_unique
std::make_shared
std::optional<>
(应该完美转发任何类型)之类的实用模板时遇到了这个真正的问题:首选哪种初始化形式?这取决于客户端代码。

没有好的答案,它们通常带有括号(理想情况下记录选择,以便调用者知道会发生什么)。惯用的现代 c++11 更喜欢到处使用大括号初始化(它避免缩小转换,避免 c++ 最令人烦恼的解析等..)


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

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
的其他构造函数)


1
投票

与任何初始化一样,

  • 当对象包含一个值或多个分段初始化的值时,请使用大括号。这包括总类别和数量。

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

  • 当根据参数计算对象的初始状态时,请使用括号。

    括号看起来更像是函数参数的序列。

此一般规则包括像

std::vector<int>
这样的容器的情况,它可以用 N 个数字副本 (
std::vector<int>(4,5)
) 或一对数字 (
std::vector<int>{4,5}
) 进行初始化。

顺便说一句,由于“统一”初始化并不是真正的包罗万象,因此不鼓励使用该术语。正式名称是brace-initialization


0
投票

一个小改进:

直接初始化:

T t(/*args*/);

列表初始化:

直接列表初始化

{}
):

T t{*list of values*};

复制列表初始化

= {}
):

T t =  {*list of values*}

官方名称在这里: https://en.cppreference.com/w/cpp/language/list_initialization

海报错误地声称正式名称是brace-initialization

std::vector
的情况下,使用直接初始化与直接列表初始化会有所不同:

std::vector<int> v1(42); // vector with 42 elements, all zeros
std::vector<int> v2{42}; // vector with 1 element with value 42

std::vector<int> v3{1,2,3};   // direct-list-initialization
std::vector<int> v4= {1,2,3}; // copy-list-initialization
© www.soinside.com 2019 - 2024. All rights reserved.