使用 const-cast 通过非常量引用延长临时的生命周期

Lifetime extension of temporary by non-const reference using const-cast

这是最近出现的问题,我觉得它不应该像它看起来那样起作用:

#include <iostream>
#include <memory>

int main()
{
    std::shared_ptr<int>& ptr = const_cast<std::shared_ptr<int>&>(
        static_cast<const std::shared_ptr<int>&>(
            std::shared_ptr<int>(
                new int(5), [](int* p) {std::cout << "Deleting!"; *p = 999;  delete(p); }
            )
        )
    );
    std::cout << "I'm using a non-const ref to a temp! " << *ptr << " ";
}

此处不需要使用 shared_ptr,但自定义删除器可以轻松演示生成对象的生命周期。 Visual Studio、Clang 和 GCC 的结果输出相同:

I'm using a non-const ref to a temp! 5 Deleting!

意味着生成的 shared_ptr 的生命周期已通过某种机制得到延长,以匹配 std::shared_ptr<int>& ptr.

的生命周期

发生了什么事?

现在,我知道对于常量引用,临时对象的生命周期将延长到引用的生命周期。但是唯一的命名对象是一个非常量引用,我希望所有其他中间表示的生命周期只等于初始化表达式。

此外,Microsoft 有一个扩展,它允许非常量引用延长绑定临时对象的生命周期,但即使禁用该扩展,这种行为似乎也存在,此外,它也出现在 Clang 和 GCC 中。

根据 this answer,我相信临时对象被隐式创建为 const,因此尝试修改 ptr 引用的对象可能是未定义的行为,但我不是确保知识告诉我关于为什么延长寿命的任何事情。我的理解是 修改 一个 const 是 UB 的行为,而不是简单地对其进行非 const 引用。

我对应该发生的事情的理解如下:

  1. Type() 创建一个没有 cv 规范的纯右值。

  2. static_cast<const Type&>(...) 将该纯右值具体化为一个 const xvalue,其生命周期等于内部表达式。然后我们创建一个 const 左值引用到那个 const xvalue。 xvalue 的生命周期被延长以匹配 const 左值引用的生命周期。

  3. const_cast<Type&>(...) 产生一个左值引用,然后分配给 ptr然后 const 左值引用过期,带走具体化的 xvalue。

  4. 我尝试读取悬空引用 ptr,但不幸的事情发生了。

我的理解有什么问题吗?为什么斜体字的位没有发生?

作为额外的奖励问题,我认为底层对象是 const 并且任何通过此路径修改它的尝试都将导致未定义的行为是否正确?

任何引用都可以延长对象的生命周期。但是,非常量引用不能像您的示例那样绑定到临时引用。您提到的 Microsoft 扩展不是 "Extend lifetime by non-const references,",而是 "Let non-const references bind to temporaries." 他们拥有该扩展是为了向后兼容他们自己以前损坏的编译器版本。

通过强制转换,您已将非常量引用强制绑定到临时对象,这似乎不是无效的,只是不寻常,因为它不能直接完成。完成绑定后,非 const 引用的生命周期延长与 const 引用相同。

更多信息:Do *non*-const references prolong the lives of temporaries?

链接的文章显然是错误的。临时对象(不一定)不是 const 对象。它不能绑定到非常量引用这一事实并不重要。它可以绑定到右值引用而无需转换,并通过此类引用进行修改。这样做没有 UB。还可以在临时对象上调用非常量成员函数。移动语义的整个概念都是基于这个事实。

绑定到普通的非常量引用并通过它进行修改是完成同一件事的不同方法。它需要演员表,但除此之外与上面的非常相似。