上次的 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 中的注释就说明了:
复制初始化列表并不会复制底层元素。
关键要点:编写类的初始化列表构造函数时,不要将元素移出初始化列表。即便已经取值,也不会传入专门的元素副本。副本仍可用作他用。