为什么在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,3 };
    std::copy( v.begin()," " ) );
    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,3}复制到v时,基础数组继续存在,直到超出范围。不幸的是,这种情况会在分配完成后立即发生,这意味着使用v的元素将在此之后出现问题。

尽管v最有可能仍具有指针和长度,但指针指向的指针已超出范围,并且内存很可能已被其他地方重用


在谈论初始化程序列表时,标准(C++20 [dcl.init.list] /6)中的相关文本指出:

[底层]数组具有与任何其他临时对象相同的生存期,只是从该数组初始化initializer_list对象可以延长数组的生存期,就像将引用绑定到临时对象一样。

由于您正在执行的操作不是初始化,因此不会发生此生存期延长的情况。

这意味着C++20 [class.temporary] /4涵盖了对基础数组的破坏:

临时对象被破坏,这是评估(按词法)包含创建点的完整表达式的最后一步。


有趣的是,cplusplus.com site for initializer_list上目前有错误代码,其中包含这个 exact 问题(我向他们提出了这个问题,不确定何时或什至他们会解决)它):

// 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(在Compiler Explorer上进行了检查)正确地将其视为有问题:

<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,30 };
  |                         ^