一个std :: initializer_list返回值的一生(lifetime of a std:

2019-07-21 05:06发布

GCC的实现销毁std::initializer_list在返回全表达年底从函数返回数组。 它是否正确?

测试案例在该程序显示值之前执行析构函数,可以使用:

#include <initializer_list>
#include <iostream>

struct noisydt {
    ~noisydt() { std::cout << "destroyed\n"; }
};

void receive( std::initializer_list< noisydt > il ) {
    std::cout << "received\n";
}

std::initializer_list< noisydt > send() {
    return { {}, {}, {} };
}

int main() {
    receive( send() );
    std::initializer_list< noisydt > && il = send();
    receive( il );
}

我认为程序应该工作。 但底层standardese是一个有点令人费解。

return语句初始化一个返回值的对象,如果它被宣布

std::initializer_list< noisydt > ret = { {},{},{} };

这将初始化一个临时initializer_list和从给定的一系列初始化的其底层阵列存储,然后初始化另一个initializer_list从第一个。 什么是数组的一生? “阵列的寿命是相同的的initializer_list对象”。 但有两个那些; 哪一个是不明确的。 在8.5.4 / 6的例子中,如果它作为宣传的,应解决的阵列具有的复制到对象寿命的模糊性。 然后返回值的数组也应该活到调用函数,它应该能够通过其绑定到一个名为参考保存它。

上LWS ,GCC错误地杀死阵列返回之前,但它保留了一个名为initializer_list每示例。 锵还正确处理的例子,但在列表永远不会被销毁的对象; 这会导致内存泄漏。 ICC不支持initializer_list的。

是我的分析是正确的?


C ++ 11§6.6.3/ 2:

支撑,初始化列表 return语句初始化对象或引用从函数从指定的初始化列表复制列表初始化(8.5.4)返回。

8.5.4 / 1:

......在副本初始化上下文列表初始化被称为副本列表初始化

8.5 / 14:

发生在形式初始化T x = a; ......被称为拷贝初始化

回到8.5.4 / 3:

对象或类型T的参考的列表的初始化被定义为如下:...

-否则,如果T是的特殊化std::initializer_list<E>一个initializer_list对象,如下所述,并用于根据从一类同一类型的对象的初始化规则,以初始化该对象(8.5)构成。

8.5.4 / 5:

类型的对象std::initializer_list<E>如如果实现分配类型EN个元素,其中N是在初始化列表中元素的数目的阵列是从初始化列表构造。 该数组的每个元素是复制初始化为初始化列表的相应元素,并且std::initializer_list<E>对象被构造来指代阵列。 如果缩小转换是必需的初始化任何元素,是形成不良的节目。

8.5.4 / 6:

阵列的寿命是相同的的initializer_list对象。 [例:

 typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 }; void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 }; } 

v1v2时, initializer_list对象和阵列createdfor { 1, 2, 3 }具有全表达寿命。 为i3 ,所述initializer_list对象和阵列具有自动寿命。 -端示例]


有一点澄清返回支撑,初始化列表

当您返回大括号括起来的裸名单,

有支撑,初始化列表return语句初始化对象或引用从函数从指定的初始化列表复制列表初始化(8.5.4)返回。

这并不意味着该对象返回到调用范围从东西复制。 例如,这是有效的:

struct nocopy {
    nocopy( int );
    nocopy( nocopy const & ) = delete;
    nocopy( nocopy && ) = delete;
};

nocopy f() {
    return { 3 };
}

这不是:

nocopy f() {
    return nocopy{ 3 };
}

复制清单初始化仅仅意味着该语法的等效nocopy X = { 3 }被用于初始化表示的返回值的对象。 这不调用一个副本,它正好是相同的阵列的寿命被延长了8.5.4 / 6的例子。

而锵和GCC都同意这一点。


其他注意事项

的审查N2640不转了这种极端情况的任何提及。 已经有关于个人特点结合在这里广泛讨论,但我没有看到他们的互动事情。

实现此,因为它涉及到由值返回一个可选的,可变长度阵列得到有毛。 因为std::initializer_list没有自己的内容,该功能必须也返回别的东西哪一样。 当传递给一个函数,这是一个简单的本地的,固定大小的数组。 但在其他方向上,VLA需要在堆栈上返回,与一起std::initializer_list的指针。 然后调用者需要被告知是否处置序列(无论他们是在栈或没有)。

这个问题是很容易的从一个lambda函数返回一个支撑,初始化列表,作为一个“自然”的方式返回几个临时的对象,而不关心他们是如何对含有偶然发现。

auto && il = []() -> std::initializer_list< noisydt >
               { return { noisydt{}, noisydt{} }; }();

事实上,这是类似我如何到达这里。 但是,这将是一个错误离开了->尾返回类型,因为拉姆达返回类型推演仅当表达式返回和支撑,初始化列表不是表达式发生。

Answer 1:

你是指在8.5.4 / 6的措辞是有缺陷的,并且是由校正(有点) DR1290 。 不要说:

阵列的寿命是相同的的initializer_list对象。

...修改后的标准现在说:

该阵列具有相同的寿命与任何其他临时对象(12.2 [class.temporary]),不同之处在于初始化initializer_list从阵列对象扩展阵列的寿命酷似结合到一个临时的参考。

因此,对于临时数组的寿命控制的写法是12.2 / 5,这表示:

一个临时的绑定到一个函数返回语句返回值的寿命没有延长; 临时在全表达的return语句结束时被销毁

因此, noisydt对象在函数返回前销毁。

直到最近,曾锵导致它失败摧毁底层数组的一个bug initializer_list在某些情况下的对象。 我已经固定,对铛3.4; 输出从锵主干测试的情况是:

destroyed
destroyed
destroyed
received
destroyed
destroyed
destroyed
received

......这是正确的,每DR1290。



Answer 2:

std::initializer_list是不是容器,不使用它是传递价值,并期望他们坚持

DR 1290改变了措辞,你也应该知道的1565和1599这还没有准备好。

然后返回值的数组也应该活到调用函数,它应该能够通过其绑定到一个名为参考保存它。

不,不遵循。 该阵列的使用期没有保持与正在扩展一起initializer_list 。 考虑:

struct A {
    const int& ref;
    A(const int& i = 0) : ref(i) { }
};

参考i结合到临时int ,然后将参考ref结合它为好,但不延伸的寿命i ,但它仍然超出范围在构造结束时,留下一个悬空参考。 你不通过结合其他参考将其扩展底层临时的一生。

如果您的代码可能会更安全1565获得批准,你做il副本不是一个参考,但这个问题仍然是开放的,甚至没有提出措辞,更别说实施经验。

即使你的例子是为了工作,对于底层数组的生命周期的措辞显然仍在改善,这将需要一段时间的编译器来实现任何最终语义看中。



文章来源: lifetime of a std::initializer_list return value