函数的const引用返回形式参数能够延长其生命周期吗?

6
const vector<int>& getVec(const vector<int> &vec)
{
    return vec;
}

我的函数是这样的,如果我使用临时对象调用getVec,这段代码会安全吗?

int main()
{
   const vector<int> &v = getVec({1,2,3,4,5});
}

我在g++和Visual Studio中运行此代码,但我得到了两个不同的结果:
  • g++告诉我:函数的向量与v相同,并且它们具有相同的值。

  • Visual Studio告诉我:函数的向量具有与v相同的地址,但v.size()仅为0,而不是5

针对这种情况是否有标准答案?一个const引用返回值只能引用函数体之前存在的值吗?


2
临时对象什么时候被删除?它不是在整个表达式结束后被删除的吗?所以在这种情况下,是在v被赋值为{1, 2, 3, 4, 5}之后吗? - Cosemuckel
3
参数在函数退出时被销毁。临时对象的生命周期延长只能在临时对象绑定到第一个引用时执行一次。该临时对象绑定到函数参数并成为函数本地对象,无法通过引用返回。 - 273K
3
@273K 临时向量在函数调用之前是否变为实体,并因此在整个表达式中保持存在,从而使复制操作安全?虽然这并不证明什么,但g++、clang++、msvc和ICX都对此表示一致同意。 - Ted Lyngmo
2
我忘记了 main 函数中的 & 符号!!! - Nater
2
@Nater 那就是未定义行为。 - Ted Lyngmo
显示剩余8条评论
2个回答

4

代码如所示,格式良好且没有任何未定义的行为。只有在实际使用v后,才会出现未定义的行为。

调用getVec时,将创建一个const vector<int>临时对象,并用{1,2,3,4,5}进行初始化,以便函数参数中的引用可以绑定到它上面。

临时对象默认情况下存活到它们被实例化的完整表达式结束(这里是表达式getVec({1,2,3,4,5})v的初始化)。将它们绑定到引用中第一次可能会延长生命周期(在某些额外条件下),但这特别不适用于函数参数中的引用,而且也不会真正有所帮助,因为函数参数中的引用本身没有更长的生命周期。

因此,在v初始化后,临时对象的生命周期足够长,以使其绑定到v,但在此之后立即被销毁。

因此,在初始化后,v将悬空,实际使用它将导致未定义的行为。但仅仅通过这种方式进行初始化是不会出现问题的。


这是否意味着通常情况下,const引用可以延长临时值的生命周期一次,但如果函数参数是const引用,则对于函数的形式参数无效,因此临时值将在V初始化后立即被销毁? - Nater
@Nater 引用的类型(const/lvalue/rvalue)与生命周期延长规则无关。生命周期最多只会在首次绑定到不是函数参数、返回值、new初始化或括号聚合初始化的引用时延长,并且如果在临时材料化和引用初始化之间的表达式仅为开始处所列项目之一,可以参考https://eel.is/c++draft/class.temporary#6获取完整的生命周期延长规则。 - user17732522
@AdrianMole 不会吧? initializer_list 的后备数组(以及 initializer_list 本身)的生命周期将与临时 std::vector 相同。 - user17732522
@user17732522 不完全正确。来自cppreference的说明:底层数组的生命周期与任何其他临时对象相同,除了使用数组初始化initializer_list对象扩展数组的生命周期,就像将引用绑定到临时对象一样(请注意,不会像使用std::vector的情况一样制作底层数组的副本)。 - Adrian Mole
@AdrianMole 我不明白那如何帮助你。initializer_list在完整表达式之后也会被销毁,其支持数组也随之销毁。如果您的意思是通过值而不是引用来完成所有操作,那么这仍然行不通,因为复制initializer_list并不能延长支持数组的生命周期。 - user17732522
显示剩余4条评论

1

如评论中已经提到的,这是未定义的行为。可以从C++11标准草案第12.2/5节中看到。

第二种情况是当引用绑定到一个临时对象时。119引用绑定的临时对象或引用绑定子对象的完整对象的临时对象,除非:

  • 在函数调用(5.2.2)包含的完整表达式完成之前,绑定到引用参数的临时对象会一直存在。
  • 返回语句(6.6.3)中绑定的临时对象并不会被延长其生命周期;该临时对象会在返回语句中的完整表达式结束时被销毁。

正如我们所看到的,无论是函数参数引用还是返回引用都不能延长临时对象{1,2,3,4,5}的生命周期。因此,在main()函数中,您有未定义的行为,可能会出现悬空引用。


这句话的字面意思是,通过函数调用绑定的临时变量存在于包含该调用的完整表达式结束之前。也就是说,在这种情况下,临时变量将在 v 复制数据后被销毁。这里没有 UB(未定义行为)。哦...算了吧,他第一次提问的问题写错了。 - ALX23z
@ALX23z 为什么需要“复制数据”?v是指向一个向量的引用。 - Adrian Mole
@AdrianMole 最初它不是一个引用。楼主后来改了它。 - ALX23z

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接