当前位置: 首页 > 知识库问答 >
问题:

std::initializer\u list返回值的生存期

林星华
2023-03-14

GCC的实现销毁从返回完整表达式末尾的函数返回的数组。这是正确的吗?

程序中的两个测试用例都显示了在可以使用该值之前执行的析构函数:

#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 );
}

我认为这个计划应该奏效。但基本标准有点复杂。

return语句初始化返回值对象,就像它被声明一样

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

这将初始化给定系列初始化器中的一个临时初始化器列表及其底层数组存储,然后从第一个初始化器列表初始化另一个初始化器列表。阵列的生存期是多少?“数组的生存期与初始化器列表的生存期相同。”但其中有两个;哪一个模棱两可。8.5.4/6中的示例,如果按照公布的方式工作,应该可以解决数组具有复制到对象的生存期这一模糊问题。然后,返回值的数组也应该存在于调用函数中,并且应该可以通过将其绑定到命名引用来保留它。

在LWS上,GCC在返回之前会错误地终止数组,但根据示例,它保留了一个命名的初始化器列表。Clang也能正确处理示例,但列表中的对象永远不会被销毁;这将导致内存泄漏。ICC根本不支持初始值设定项列表。

我的分析正确吗?

C 11§6.6.3/2:

带有大括号init list的return语句通过从指定的初始值设定项列表复制列表初始化(8.5.4)来初始化要从函数返回的对象或引用。

8.5.4/1:

…复制初始化上下文中的列表初始化称为复制列表初始化。

8.5/14:

以T x=a的形式出现的初始化 …称为复制初始化。

回到8.5.4/3:

类型为T的对象或引用的列表初始化定义如下:…

-否则,如果T是std::initializer\u list的特化

8.5.4/5:

类型为std::initializer\u list的对象

8.5.4/6:

数组的生存期与initializer\u列表的生存期相同。[示例:

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 };
 }

对于v1和v2,为{code>{1,2,3}创建的对象和数组具有完整的表达式生存期。对于i3,initializer\u list对象和数组具有自动生存期。-结束示例]

返回括号中的空列表时,

带有大括号init 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中延长阵列生存期的示例相同。

Clang和GCC在这一点上达成了一致。

对N2640的回顾没有提到这个角落案例。关于这里组合的各个特性已经有了广泛的讨论,但是我没有看到任何关于它们交互的东西。

实现这一点很困难,因为它归结为按值返回可选的可变长度数组。由于std::initializer\u列表不拥有其内容,因此该函数还必须返回其他内容。当传递给函数时,这只是一个局部的、固定大小的数组。但在另一个方向,VLA需要与std::initializer\u list一起返回到堆栈上。然后需要告诉调用方是否要处理该序列(无论它们是否在堆栈上)。

从lambda函数返回带括号的初始化列表很容易发现这个问题,这是一种“自然”的方式来返回一些临时对象,而不关心它们是如何包含的。

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

事实上,这与我到达这里的方式相似。但是,省略将是一个错误-


共有2个答案

公良骁
2023-03-14

您在8.5.4/6中提到的措辞有缺陷,并已通过DR1290进行了更正(有些)。而不是说:

数组的生存期与initializer\u列表的生存期相同。

。。。修订后的标准现在规定:

该数组与任何其他临时对象(12.2[类.临时])具有相同的生存期,只是从数组初始化初始化器列表对象会延长数组的生存期,就像将引用绑定到临时对象一样。

因此,临时数组生命周期的控制措辞是12.2/5,即:

临时绑定到函数返回语句中返回值的生命周期不会延长;临时在返回语句中的完整表达式末尾被销毁

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

直到最近,Clang还存在一个bug,导致它在某些情况下无法销毁initializer\u list对象的底层数组。我已经修复了Clang 3.4的问题;来自Clang trunk的测试用例的输出是:

destroyed
destroyed
destroyed
received
destroyed
destroyed
destroyed
received

。。。根据DR1290,这是正确的。

应向晨
2023-03-14

initializer\u list不是一个容器,不要使用它来传递值并期望它们持续存在

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

然后,返回值的数组也应该存在于调用函数中,并且应该可以通过将其绑定到命名引用来保留它。

不,那不符合。数组的生命周期不会随着initializer_list一起延长。考虑:

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

引用i绑定到临时int,然后引用ref也绑定到它,但这并不会延长i的生存期,它仍然在构造函数末尾超出范围,留下悬空引用。不能通过绑定对基础临时对象的另一个引用来延长其生存期。

如果1565获得批准并且您使il成为副本而不是引用,您的代码可能会更安全,但该问题仍然存在,甚至没有建议的措辞,更不用说实现经验了。

即使您的示例是为了工作,关于底层数组生存期的措辞显然仍在改进中,编译器需要一段时间才能实现最终确定的语义。

 类似资料:
  • 生成器返回值 PHP7支持通过Generator::getReturn获取生成器方法return的返回值。 PHP5中我们约定使用Generator最后一次yield值作为返回值。 <?php final class AsyncTask { public function begin() { return $this->next(); } //

  • 我有一个存储过程,它接受参数并返回一个长值。 我如何使用jdbcTemplate来调用这个存储过程并获得返回的值。 这样对吗?

  • <代码> 现在,我听到了关于专门化和的矛盾信息-有人告诉我,向std命名空间添加任何东西都会导致未定义的行为,而我也被告知,您可以提供自己的和的专门化。 这就是我现在正在做的: 这确实有效:http://ideone.com/wHVfkh 但我想知道,这样做有什么坏处?有更好的方法吗?

  • 查找表示整数为所需的最小位 为什么为值0返回0,难道不应该返回1吗。因为表示0所需的位数是1。 另外,我认为公式中的是一个偏移量

  • 根据函数或者结构体方法的返回值,returnHandle 插件将自动将内容写入到 ResponseWriter. 目前支持的返回值及对应的行为如下: string 返回string,将会把string转为[]byte同时写入到ResponseWriter []byte 返回[]byte将会直接写入ResponseWriter error 返回错误,如果error不为nil, 则写入返回头500,内

  • 问题内容: 我在使用Ajax时遇到问题。 问题是,在获得ajax响应之前,它会返回cnt。因此它总是返回NULL。 有没有办法使正确的返回响应值? 谢谢! 问题答案: 由于AJAX请求是异步的,因此您的cnt变量将在请求返回并调用成功处理程序之前返回。 我建议重构您的代码以解决此问题。 一种方法是从AJAX请求的成功处理程序中调用调用了GetGrantAmazonItemCnt()的任何函数,此方