这个问题是一个跟进为什么std::initializer_list被允许不指定大小和被堆栈分配在同一时间?
简短的回答是,调用带有大括号列表foo({2, 3, 4, 5, 6});
的函数在概念上在调用之前在堆栈空间中创建一个临时数组,然后传递初始化列表(如string_view)只是引用这个本地临时数组(可能在寄存器中):
int __tmp_arr[5] {2, 3, 4, 5, 6};
foo(std::initializer_list{arr, arr + 5});
现在考虑下面的例子,我嵌套了一个对象"ref"的initializer_lists。这个ref对象在变量中递归地存储原语类型或initializer_list。我现在的问题是:这是未定义行为吗?它似乎可以与我的代码一起工作,但它是否符合标准?我怀疑的原因是,当内部构造函数调用嵌套的用大括号括起来的列表返回时,初始化列表引用的临时数组可能会无效,因为堆栈指针被重置(因此在变体中保存initializer_list保留了一个无效对象)。写入后续内存将覆盖由初始化列表引用的值。我这么想错了吗?
CompilerExplorer
#include <variant>
#include <string_view>
#include <type_traits>
#include <cstdio>
using val = std::variant<std::monostate, int, bool, std::string_view, std::initializer_list<struct ref>>;
struct ref
{
ref(bool);
ref(int);
ref(const char*);
ref(std::initializer_list<ref>);
val value_;
};
struct container
{
container(std::initializer_list<ref> init) {
printf("---------------------n");
print_list(init);
}
void print_list(std::initializer_list<ref> list)
{
for (const ref& r : list) {
if (std::holds_alternative<std::monostate>(r.value_)) {
printf("intn");
} else if (std::holds_alternative<int>(r.value_)) {
printf("intn");
} else if (std::holds_alternative<bool>(r.value_)) {
printf("booln");
} else if (std::holds_alternative<std::string_view>(r.value_)) {
printf("string_viewn");
} else if (std::holds_alternative<std::initializer_list<ref>>(r.value_)) {
printf("initializer_list:n");
print_list(std::get<std::initializer_list<ref>>(r.value_));
}
}
}
};
ref::ref(int init) : value_{init} { printf("%d storedn", init); }
ref::ref(bool init) : value_{init} { printf("%s storedn", init ? "true" : "false"); }
ref::ref(const char* init) : value_{std::string_view{init}} { printf("%s storedn", init); }
ref::ref(std::initializer_list<ref> init) : value_{init} { printf("initializer_list storedn", init); }
int main()
{
container some_container = { 1, true, 5, { {"itemA", 2}, {"itemB", true}}};
}
输出:
1 stored
true stored
5 stored
itemA stored
2 stored
initializer_list stored
itemB stored
true stored
initializer_list stored
initializer_list stored
---------------------
int
bool
int
initializer_list:
initializer_list:
string_view
int
initializer_list:
string_view
bool
首先,复制std::initializer_list
并不复制底层对象。(cppreference)
和
container some_container = { 1, true, 5, { {"itemA", 2}, {"itemB", true}}};
实际上编译成类似于
的东西container some_container = { // initializer_list<ref>
ref{1},
ref{true},
rer{5},
ref{ // initializer_list<ref>
ref{ // initializer_list<ref>
ref{"itemA"},
ref{2}
},
ref{ // initializer_list<ref>
ref{"itemB"},
ref{true}
}
}
};
和所有这些对象的生命周期*在完整表达式结束时结束(这里的;
)
*包括所有initializer_list<ref>
,它们的底层数组,以及所有属于ref
的对象,但不包括构造函数中的对象(复制省略可以适用)
- 是的,可以在构造函数中使用这些对象。
- 这些对象在
;
之后消失了,所以你不应该再使用存储的对象(在initializer_list
中)
带噪声析构函数的godbolt示例及施工后的动作