返回常量引用 vs 临时对象

13

我想知道为什么返回局部对象的const reference是非法的,而只要将其分配给const reference返回局部对象就是合法的?

vector<int> f_legal() {
    vector<int> tempVec;
    tempVec.push_back(1);
    return tempVec;
}

const vector<int>& f_illegal() {
    vector<int> tempVec;
    tempVec.push_back(1);
    return tempVec;
}

void g() {
    const vector<int>& v1 = f_legal(); // legal
    const vector<int>& v2 = f_illegal(); // illegal
}

编辑: 我的观点是,如果将const引用分配给返回的本地变量是合法的,那么将const引用分配给返回的本地变量的const引用也应该是合法的,对吧?


3
是的,在C++中,局部const引用定义清晰,它会延长与其绑定的临时变量的生命周期。 - Mark B
3
是的,它是合法的。标准中有一个特殊情况,只要在当前作用域中存在对该局部对象的常量引用,它就会保持存活状态。 - Mark Ransom
3个回答

15

返回对局部变量的引用是非法的(未定义行为)。没有 constmutable 子句。

这是因为局部函数变量具有自动存储期。它们在函数退出后被“销毁”。如果函数返回对这样一个变量的引用,则该引用被称为悬空引用:它指向一个不再存在的对象。

第一个是合法的,因为有一个特殊的 C++ 规则:将对 prvalue 的引用初始化会扩展该临时对象的生命周期到引用的生命周期。


这个规则实际上与 const 没有任何关系 -- 任何直接绑定到临时对象的引用都会延长该临时对象的生命周期。在 C++98 中,其他规则规定只有 const 引用可以直接绑定到临时对象,这些规则现在已经改变,这对生命周期延长规则产生了连锁反应。 - Ben Voigt
@BenVoigt 这是我第一次听到这个。你能提供一个参考吗? - bolov
@BenVoigt 谢谢您,非常有帮助。 - bolov
1
@Maggyero:这不是直接绑定到临时变量的引用,而是编译错误。但是int&& r = 3;可以编译,并且确实延长了临时变量的生命周期。 - Ben Voigt
2
@Maggyero:你也可以从类型不匹配或其他限定符不匹配(例如尝试删除const_cast而没有删除volatile)中获得引用绑定编译错误。如果我说一个引用直接绑定的,那么自然地,我只谈论可能的情况。 - Ben Voigt
显示剩余8条评论

14
即使将其分配给const引用,返回值仍然声明为按值传递,这意味着它将被复制[1]到外部作为临时对象,然后绑定到const引用。将临时对象绑定到const引用是可以的,该对象在超出const引用的生命周期之前不会被销毁。
另一方面,返回局部变量的引用是非法的。当函数返回时,局部变量将被销毁,这意味着外部引用将悬空。 编辑

我的观点是,如果将const ref分配给返回的局部变量是合法的,那么将const ref分配给返回的局部变量的const ref也应该是合法的,对吗?

关键在于第一种情况不是将const ref分配给返回的局部变量,而是将const ref分配给返回的临时变量。(可能是从局部变量复制过来的。)


[1]根据RVO技术,副本可能会被省略。


你确定这总是成立的吗?即使考虑到RVO/NRVO? - James Adkison
1
@JamesAdkison:将会有一个临时对象,引用将绑定到该临时对象。如果结果可以直接构造在临时对象中,则可能省略复制。 - Ben Voigt
据我所知,RVO 作为一种优化方式不会改变任何规则。这个“规则”是:返回值按值传递(即复制)。 - 463035818_is_not_a_number
这不就是问题所在吗? - juanchopanza
@JamesAdkison,实际上我现在认为我写的是无意义的。难道不是这种情况吗,RVO是一种例外,其中优化可能会改变程序流程(即复制的任何副作用)? - 463035818_is_not_a_number
显示剩余2条评论

1
很可能是因为这会完全破坏基于堆栈的调用约定,而这种约定已经为我们服务了几十年...几乎每个CPU都假定这种约定。

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