关于使用 { } 在 C++ 中初始化向量

About initializing a vector in C++ with { }

在 Stroustrup 的书 "Programming: Principles and Practices of Programming Using C++ (Second Edition)" 中,作者在自己的结构中创建了三个向量,如下所示:

struct Day {
    vector<double> hour{ vector<double>(24,not_a_reading) };
};

struct Month { // a month of temperature readings
    int month{ not_a_month }; // [0:11] January is 0
    vector<Day> day{ 32 }; // [1:31] one vector of readings per day
};

struct Year { // a year of temperature readings, organized by month
    int year; // positive == A.D.
    vector<Month> month{ 12 }; // [0:11] January is 0
};

我的问题是,为什么 vector<Day> day{32}; 创建一个大小为 32 的 Day 类型的向量,而 vector<int>{32}; 创建一个大小为 1 的向量?

当初始值设定项是初始值设定项列表时,首先考虑接受 std::initializer_list 的构造函数。

所以对于这个声明

std::vector<int> v{32};

使用了class std::vector 的constrictor,其第一个参数类型为std::initializer_list<T>。

vector(initializer_list<T>, const Allocator& = Allocator());

在这种情况下,std::initializer_list<int> 仅包含一个等于 32 的元素。

在此声明中

std::vector<Day> day{32};

初始化列表 { 32 } 无法转换为 std::initializer_list<Day>。所以接受初始化列表的构造函数是不合适的。

在这种情况下,编译器会考虑另一个构造函数并找到接受整数作为向量中元素数的构造函数。

explicit vector(size_type n, const Allocator& = Allocator());

注意这个构造函数是显式的。所以例如你不能写

std::vector<Day> day = {32};

来自 C++ 17 标准(13.3.1.7 通过列表初始化进行初始化)

1 When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

(1.1) — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.

(1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

C++11 添加了 type name{...}; 语法(称为 统一初始化),应该成为 the 新的初始化语法,取代其他替代方法。它比替代品有一些优势,因此有人积极推广它。

但它有一个问题:它在一些 class 没有考虑到它的情况下表现得很奇怪(主要是标准容器,它们是在它出现之前设计的)。

发生的事情是,vector<T> name{...} 首先尝试使用带有 std::initializer_list 参数的构造函数(vector<int> name{42}; 成功,用单个数字 42 初始化向量) ,如果失败,它会尝试使用其他构造函数(vector<Day> int{42}; 最终使用带有单个 size_t size 参数的构造函数,用 42 个默认构造的对象填充向量)。

如果您认为这种行为很奇怪,那么您并不孤单。

经验法则是:

  • 在处理容器(或完全)时避免使用 type name{...} 语法。

  • 如果要用列表初始化容器,使用vector<T> name = {...};
    vector<Day> name = {42}; 不会编译。)

  • 如果你想使用容器的其他构造函数,请使用vector<T> name(...);
    vector<int> name(42); 将用 42 个零填充向量。)

请注意,vector<T> name(...); 在 class 范围 中 将不起作用。您可以改用 vector<T> name = vector<T>(...);

std::vector has 适合我们的填充和初始化列表构造函数。

Day 无法值初始化为 32,因此调用填充构造函数。

这可以通过添加适当的 Day 构造函数轻松验证:

struct Day {
    Day(int) {}
    vector<double> hour{ vector<double>(24,not_a_reading) };
};

struct Month { // a month of temperature readings
    int month{ not_a_month }; // [0:11] January is 0
    vector<Day> day{ 32 }; // [1:31] one vector of readings per day
};

struct Year { // a year of temperature readings, organized by month
    int year; // positive == A.D.
    vector<Month> month{ 12 }; // [0:11] January is 0
};

// vector<Day> day{32};
// day.size() == 1