玩转 C++ [测验系列 – 第 2 部分]

Blog
Author:
Joseph SibonyJoseph Sibony
Published On:
3月 13, 2023
Estimated reading time:
1 minute

上次的 C++ 知识测验大获成功。许多 C++ 拥趸者都参与了进来,其中一名幸运儿荣获冠军。因此,我们决定将这个测验变成传统,让它传承下来。现在,新的 C++ 趣味测验来啦!

感谢 CppQuiz 的 Anders Schau Knatten 制作了本次测验。

希望你们喜欢。

C++ 知识测验

#include <initializer_list>
#include <iostream>

class C {
public:
    C() = default;
    C(const C&) { std::cout << 1; }
};

void f(std::initializer_list<C> i) {}

int main() {
    C c;
    std::initializer_list<C> i{c};
    f(i);
    f(i);
}

提示:

如果只调用 f(i) 一次,那么结果会相同。

答案:

程序保证输出 1

解释:

复制时,C 类打印 1。那么它什么时候复制?

标准中 [dcl.init.list]§11.6.4¶5 解释了 initializer_list 的结构:

从初始化列表中构造类型为 std::initializer_list<E> 的对象,就像此实现操作生成和具现化了类型为“array of N const E”的 prvalue (7.4)。其中 N 表示初始化列表中元素的数量。该数组的各元素根据初始化列表的相应元素进行复制初始化,而 std::initializer_list<E> 对象被构造为引用该数组。

因此,构造初始化列表 i 时,我们可以想象创建一个临时的 C 数组,其中元素为复制初始化。由于 c 为 lvalue,因此执行复制(而非移动),并打印 1。

然后,调用 f 两次,为新建的 initializer_list 取值。因此,每次调用都会生成一个 initializer_list 副本。这是否会像向量或列表取值那样,生成初始化列表元素的副本?不会。正如我们在上述引文末尾所看到的那样:

std::initializer_list<E> 对象被构造为引用该数组。

initializer_list 仅引用该数组,没有副本。[initializer_list.syn]§21.9.1¶1 中的注释就说明了:

复制初始化列表并不会复制底层元素。

关键要点:编写类的初始化列表构造函数时,不要将元素移出初始化列表。即便已经取值,也不会传入专门的元素副本。副本仍可用作他用。