为什么在VS2019中std::initializer_list的初始化似乎失败了



以下代码在以发布模式编译的Visual Studio 2019上失败。

#include <iostream>
#include <iterator>
#include <initializer_list>
int main( int, char** )
{
std::initializer_list<int> v = {};
std::initializer_list<int> i = { 1, 2, 3 };
v = i;
std::copy( v.begin(), v.end(), std::ostream_iterator<int>( std::cout, " " ) );
std::cout << std::endl;
v = { 1, 2, 3 };
std::copy( v.begin(), v.end(), std::ostream_iterator<int>( std::cout, " " ) );
std::cout << std::endl;
}

v的第二次初始化似乎失败了,输出如下:

1 2 3
17824704 10753212 0

但是在调试模式下构建或使用其他编译器(gcc、clang(构建时。输出如预期:

1 2 3
1 2 3

这可能是什么原因?

需要明确的是,v的唯一初始化发生在以下行中:

std::initializer_list<int> v = {};

另外两个是分配而不是初始化:

v = i;
v = { 1, 2, 3 };

正是这两个作业之间的差异提供了解决方案。

复制初始化程序列表并不一定要复制底层元素-初始化程序列表通常是轻量级的,通常只作为指针和长度来实现。

因此,当您将i分配给v时,i(因此也是v(的底层数组将继续存在,直到main结束时超出范围。没有问题。

当您将{1, 2, 3}复制到v时,底层数组继续存在,直到它超出范围。不幸的是,一旦分配完成,就会发生这种情况,这意味着在那之后使用v的元素将是有问题的。

尽管v很可能仍然有一个指针和长度,但指针指向的东西已经超出了范围,内存很可能已经被重新用于其他用途。


当谈到初始化程序列表时,标准(C++20 [dcl.init.list] /6(中的相关文本表示:

〔underlying〕数组与任何其他临时对象具有相同的生存期,只是从数组初始化initializer_list对象会延长数组的生存期——就像将引用绑定到临时对象一样。

由于您所做的不是初始化,因此不会发生此寿命延长。

这意味着C++20 [class.temporary] /4:涵盖了底层阵列的破坏

临时对象作为评估完整表达式的最后一步被销毁,该表达式(词法上(包含创建它们的点。


有趣的是,initializer_list的cplusplus.com网站上目前有关于这个确切问题的错误代码(我已经向他们提出了这个问题,不确定他们何时甚至是否会解决(:

// initializer_list example
#include <iostream>          // std::cout
#include <initializer_list>  // std::initializer_list
int main ()
{
std::initializer_list<int> mylist;
mylist = { 10, 20, 30 };
std::cout << "mylist contains:";
for (int x: mylist) std::cout << ' ' << x;
std::cout << 'n';
return 0;
}

虽然这在某些情况下可能有效,但这并不能保证,事实上,gcc 10.2(在编译器资源管理器上检查过(正确地认为它有问题:

<source>: In function 'int main()':
<source>:8:25: warning: assignment from temporary 'initializer_list'
does not extend the lifetime of the underlying array
[-Winit-list-lifetime]
8 |   mylist = { 10, 20, 30 };
|                         ^

最新更新