本文介绍了std :: initializer_list返回值的生命周期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! GCC的实现破坏了从返回full-expression末尾的函数返回的 std :: initializer_list 数组。这是正确的吗? 这个程序中的两个测试用例都显示在可以使用值之前执行的析构函数: #include< initializer_list> #include< iostream> struct noisydt {〜noisydt(){std :: cout< destroyed\\\; } }; void receive(std :: initializer_list< noisydt> il){ std :: cout< received\\\; } std :: initializer_list< noisydt> send(){ return {{},{},{}}; } int main(){ receive(send()); std :: initializer_list< noisydt> &&& il = send(); receive(il); } 我认为程序应该工作。 返回语句初始化一个返回值对象,就像它被声明 std :: initializer_list< noisydt> ret = {{},{},{}}; 这会初始化一个临时 initializer_list 数组存储从给定的一系列初始化器,然后初始化另一个 initializer_list 从第一个。什么是数组的生命周期? 数组的生命周期与 initializer_list 对象的生命周期相同。但有两个;哪一个是模糊的。在8.5.4 / 6中的示例,如果它如所通告的那样工作,应当解决数组具有复制到对象的生存期的模糊性。 在 http://liveworkspace.org/code/2ZlWsj%240\"> LWS ,GCC在返回之前错误地删除了数组,但它保留了一个名为 initializer_list 的例。 Clang还会正确处理该示例,但列表中的对象永远不会被销毁;这将导致内存泄漏。 ICC不支持 initializer_list 。 我的分析结果是否正确? C ++ 11§6.6.3/ 2: 8.5.4 / 1: 8.5 / 14: 8.5.4 / 5(对象的初始化规则) : 8.5.4 / 6:如果需要缩小转换以初始化任何元素, / p> a braced-init-list 当您返回括号括起来的裸列表时,这并不意味着返回到调用范围的对象是从某个东西复制的。例如,这是有效的: 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示例相同。 而Clang和GCC做同意这一点。 其他注意事项 N2640 不会提及这种情况。关于这里的个别功能已经进行了广泛的讨论,但我没有看到任何关于他们的互动。 实现这是毛茸茸的,因为它返回一个可选的,可变长度数组的值。因为 std :: initializer_list 不拥有它的内容,该函数还必须返回其他的东西。当传递给一个函数时,这只是一个局部的,固定大小的数组。但在另一个方向,VLA需要在栈上返回,以及 std :: initializer_list 的指针。然后调用者需要被告知是否处理序列(不管他们是否在栈上)。 这个问题很容易绊倒一个来自lambda函数的braced-init-list,作为一种自然的方式返回一些临时对象而不关心它们如何包含。 code>自动&& il = []() - > std :: initializer_list< noisydt> {return {noisydt {},noisydt {}}; }(); 确实,这是类似于我到达这里。但是,省略 - > trailing-return-type将是一个错误,因为lambda返回类型的扣除只发生在返回一个表达式时,而braced-init -list不是表达式。解决方案您在8.5.4 / 6中提到的措辞有缺陷,有些), DR1290 。而不是说: ...修改后的标准现在说: 该数组与任何其他临时对象(12.2 [class.temporary])具有相同的生命周期,除了从数组初始化一个 initializer_list 因此,临时数组生命周期的控制措辞是12.2 / 5,它表示:因此, noisydt code>对象在函数返回之前被销毁。 直到最近,Clang还有一个错误导致它无法破坏 initializer_list 对象。我已经修复了Clang 3.4;您的测试用例从Clang trunk的输出是: 销毁销毁销毁收到销毁销毁销毁收到 ...这是正确的,根据DR1290。 GCC's implementation destroys a std::initializer_list array returned from a function at the end of the return full-expression. Is this correct?Both test cases in this program show the destructors executing before the value can be used:#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 );}I think the program should work. But the underlying standardese is a bit convoluted.The return statement initializes a return value object as if it were declared std::initializer_list< noisydt > ret = { {},{},{} };This initializes one temporary initializer_list and its underlying array storage from the given series of initializers, then initializes another initializer_list from the first one. What is the array's lifetime? "The lifetime of the array is the same as that of the initializer_list object." But there are two of those; which one is ambiguous. The example in 8.5.4/6, if it works as advertised, should resolve the ambiguity that the array has the lifetime of the copied-to object. Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.On LWS, GCC erroneously kills the array before returning, but it preserves a named initializer_list per the example. Clang also processes the example correctly, but objects in the list are never destroyed; this would cause a memory leak. ICC doesn't support initializer_list at all.Is my analysis correct?C++11 §6.6.3/2:8.5.4/1:8.5/14:Back to 8.5.4/3:8.5.4/5:8.5.4/6:A little clarification about returning a braced-init-listWhen you return a bare list enclosed in braces, This doesn't imply that the object returned to the calling scope is copied from something. For example, this is valid:struct nocopy { nocopy( int ); nocopy( nocopy const & ) = delete; nocopy( nocopy && ) = delete;};nocopy f() { return { 3 };}this is not:nocopy f() { return nocopy{ 3 };}Copy-list-initialization simply means the equivalent of the syntax nocopy X = { 3 } is used to initialize the object representing the return value. This doesn't invoke a copy, and it happens to be identical to the 8.5.4/6 example of an array's lifetime being extended.And Clang and GCC do agree on this point.Other notesA review of N2640 doesn't turn up any mention of this corner case. There has been extensive discussion about the individual features combined here, but I don't see anything about their interaction.Implementing this gets hairy as it comes down to returning an optional, variable-length array by value. Because the std::initializer_list doesn't own its contents, the function has to also return something else which does. When passing to a function, this is simply a local, fixed-size array. But in the other direction, the VLA needs to be returned on the stack, along with the std::initializer_list's pointers. Then the caller needs to be told whether to dispose of the sequence (whether they're on the stack or not).The issue is very easy to stumble upon by returning a braced-init-list from a lambda function, as a "natural" way to return a few temporary objects without caring how they're contained.auto && il = []() -> std::initializer_list< noisydt > { return { noisydt{}, noisydt{} }; }();Indeed, this is similar to how I arrived here. But, it would be an error to leave out the -> trailing-return-type because lambda return type deduction only occurs when an expression is returned, and a braced-init-list is not an expression. 解决方案 The wording you refer to in 8.5.4/6 is defective, and was corrected (somewhat) by DR1290. Instead of saying:... the amended standard now says:Therefore the controlling wording for the lifetime of the temporary array is 12.2/5, which says:Therefore the noisydt objects are destroyed before the function returns.Until recently, Clang had a bug that caused it to fail to destroy the underlying array for an initializer_list object in some circumstances. I've fixed that for Clang 3.4; the output for your test case from Clang trunk is:destroyeddestroyeddestroyedreceiveddestroyeddestroyeddestroyedreceived... which is correct, per DR1290. 这篇关于std :: initializer_list返回值的生命周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
09-02 11:21