将 const 引用返回到本地对象时究竟会发生啥?

Posted

技术标签:

【中文标题】将 const 引用返回到本地对象时究竟会发生啥?【英文标题】:What exactly happens when returning const reference to a local object?将 const 引用返回到本地对象时究竟会发生什么? 【发布时间】:2012-07-31 19:33:38 【问题描述】:
struct A 
    A(int) : i(new int(783)) 
        std::cout << "a ctor" << std::endl;
    

    A(const A& other) : i(new int(*(other.i))) 
        std::cout << "a copy ctor" << std::endl;
    

    ~A() 
        std::cout << "a dtor" << std::endl;
        delete i;
    

    void get() 
        std::cout << *i << std::endl;
    

private:
    int* i;
;

const A& foo() 
    return A(32);


const A& foo_2() 
    return 6;


int main()

    A a = foo();
    a.get();

我知道,返回对本地值的引用是不好的。但是,另一方面,const 引用应该延长一个临时对象的生命周期。

此代码产生一个 UB 输出。所以没有延长寿命。

为什么?我的意思是有人可以逐步解释发生了什么吗?

我的推理链哪里出错了?

foo():

    A(32) - 演员

    return A(32) - 创建并返回对本地对象的 const 引用

    A a = foo(); - a被foo()返回值初始化,返回值超出范围(out of expression)被销毁,但是a已经被初始化了;

(但实际上析构函数是在复制构造函数之前调用的)

foo_2():

    return 6 - 隐式创建类型 A 的临时对象,创建对该对象的 const 引用(延长其寿命)并返回

    A a = foo(); - a被foo()返回值初始化,返回值超出范围(out of expression)被销毁,但是a已经被初始化了;

(但实际上析构函数是在复制构造函数之前调用的)

【问题讨论】:

"const 引用应该延长一个临时对象的生命周期" 不扩展它. 我认为这就是亚历山大在延长寿命方面所说的:herbsutter.com/2008/01/01/… @Giel:不。常量引用可以延长临时对象的生命周期。在使用临时对象时,常量引用和非常量引用完全不同。在这种情况下,它的工作方式与 OP 似乎期望的不同。 @AndreyT:对,vmpstr 给出的链接很好地解释了它。然而,在 OP 的情况下,reference 是返回的临时对象,而不是引用的对象。因此,正如预期的那样,它不会将临时的生命周期延长到产生它的范围之外。我相信后者在所有情况下仍然是正确的:局部对象(临时或作为局部变量)超出范围。 不,他们的生活不会超出他们的范围,但他们的尸体通常仍然在那里并且“似乎”正在运作和运作,这可能会令人困惑,因为返回参考“似乎工作正常“在某些情况下,但在其他情况下则不然。虽然这是一些额外的工作,但为了防止这种混淆(以及使用过时指针的混淆),我喜欢在删除它们后将析构函数中的成员指针清空,并且在某些情况下将成员变量设置为安全的无效状态,所以如果有人尝试使用已删除的对象,这很明显。 【参考方案1】:

语言规范中明确规定了每个特定上下文的临时生命周期延长规则。它说

12.2 临时对象

5 第二个上下文是引用绑定到临时的。 [...] 临时绑定到函数返回语句中的返回值 (6.6.3) 一直持续到函数退出。 [...]

您的临时对象在函数退出时被销毁。这发生在接收者对象的初始化开始之前。

您似乎认为您的临时工应该以某种方式活得比这更长。显然,您正在尝试应用这条规则,即临时文件应该一直存在到完整表达式结束。但该规则不适用于在函数内部创建的临时对象。此类临时人员的生命周期由他们自己的专用规则管理。

如果有人尝试使用返回的引用,您的 foofoo_2 都会产生未定义的行为。

【讨论】:

但是如果“在函数返回语句 (6.6.3) 中的临时绑定到返回值一直持续到函数退出” foo_3()return A( 54);? @Alexander - 不,A foo_3() 返回值的副本。复制的值不会在函数结束时销毁。当您返回一个引用时,该引用仍然存在 - 它不再引用任何内容。 我明白了.. 但是应该在表达式 A a = foo_3(); 中调用复制 ctor 两次第一次从临时本地复制返回值,第二次初始化 A a。但它只调用一次。还是只是优化? @Alexander:当你使用A foo_3()return A(54); 时,你有两个不同的概念临时性。第一个是您明确创建的 - A(54)。第二个是一个特殊的内部“过渡”临时文件,它在函数退出后保存结果。 A(54) 临时被复制到“过渡”临时,A(54) 被销毁。 “过渡”临时对象的寿命更长,可用作接收对象的初始值设定项。 对于A a = foo();,如果返回的对象被销毁,那么分配给a的是什么?它指的是哪里?【参考方案2】:

您误解了“直到函数退出”。如果您真的想使用 const 引用来延长对象的寿命,超出foo,请使用

A foo() 
    return A(32);

int main() 
    const A& a = foo();

你必须从foo返回按值,然后使用一个常量引用来引用返回值,如果你想以你期望的方式扩展东西。

正如@AndreyT 所说,对象在具有const &amp; 的函数中被销毁。您希望您的对象在foo 之后仍然存在,因此您应该拥有const &amp; (或&amp;)在foofoo 的返回类型中的任何位置。第一次提到const &amp; 应该在main 中,因为那是应该让对象保持活动状态的函数。

您可能认为这个按值返回的代码很慢,因为在返回中似乎有 A 的副本,但这是不正确的。在大多数情况下,编译器只能在 A 的最终位置(即调用函数的堆栈上)构造一次,然后设置相关引用。

【讨论】:

以上是关于将 const 引用返回到本地对象时究竟会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

返回对临时对象成员的 const 引用

启用复杂脚本支持后究竟会发生啥?

当我设置 IIS 池的 LoadUserProfile 时究竟会发生啥?

在 ClickHouse 中重命名表时究竟会发生啥?

当 iOS 为应用程序安装更新时,究竟会发生啥?

当您引用一个 DOM 对象 (var o=docume...) 并通过 parent.innerHTML='' 删除该 DOM 对象时会发生啥?