C++17 和静态临时生命周期的引用扩展

Posted

技术标签:

【中文标题】C++17 和静态临时生命周期的引用扩展【英文标题】:C++17 and reference extension of static temporary lifetime 【发布时间】:2018-09-26 03:56:50 【问题描述】:

我有一些代码试图做一种单例多态性,像这样:

// header
struct B

    virtual ~B() = default;
    virtual void F() = 0;

    static const B& Type1;
    static const B& Type2;
;

// cpp
struct D1 : B

    void F() override;
;

struct D2 : B

    void F() override;
;

const B& B::Type1 = D1();
const B& B::Type2 = D2();

// consumer
class Usage

public:
    Usage() : m_b(&B::Type1) 

    void UseType1()  m_b = &B::Type1; 
    void UseType2()  m_b = &B::Type2; 
    void F() const  m_b->F(); 
private:
    const B* m_b;
;

因此,消费类总是使用这些实例之一,但具体的一个是在运行时决定的。 (它在顶层使用多态性引用而不是指针,以便正确删除对象,但也避免像智能指针那样将它们放在堆上。)

据我了解,对临时对象的 const 引用应该会在引用的生命周期内延长该临时对象的生命周期(关于生命周期的一些警告通常在函数退出或类似的情况下结束)。由于这些特定引用具有静态范围,因此它们应该在进程的生命周期内存在,因此也可以将临时引用保持在该范围内。

此代码在 VS2015 和 VS2017 15.8.5 默认 C++14 编译模式下按预期工作。

但是,如果我将 VS2017 切换到 C++17 编译模式,那么(没有任何编译器警告)这会在运行时崩溃,因为某些特定的 const B* 指向一个具有完全不相关的 vtable 的对象——即。某些东西踩在了本应为其中一个实例保留的内存上。我认为这意味着临时文件被过早地销毁了。

我可以通过避免使用临时来使其行为符合预期:

static const D1 GlobalType1;
static const D2 GlobalType2;
const B& B::Type1 = GlobalType1;
const B& B::Type2 = GlobalType2;

这是编译器错误还是代码中的标准违规?

【问题讨论】:

可能与this thread中的bug有关 我会说这是一个编译器错误,因为您所做的是正确的。另外,Godbolt 是你的朋友,所以看看 gcc 和 clang 对此事的看法。对于无法编译 constexpr void f(void) constexpr void f() 相比作为回归的编译器,一切皆有可能。 Godbolt 实际上并没有运行生成的代码,因此对于运行时错误并没有太大的兴趣。而且它包含的VS2017版本太旧了。 但是 FWIW 可以在gist.github.com/uecasm/108a8a495d9bef55fe34304dea5aa1e1 找到一个在 VS2017 中以 C++17 模式崩溃的完整示例。预期的行为是打印“Called D2”。 【参考方案1】:

由于 cmets 中的结论似乎是这确实是一个编译器错误,我有 reported an issue。

在结束之前将问题留待一段时间。

【讨论】:

以上是关于C++17 和静态临时生命周期的引用扩展的主要内容,如果未能解决你的问题,请参考以下文章

在 C++17 中在对象的生命周期之外调用非静态成员函数

临时子表达式的临时生命周期,绑定到引用

为啥我们可以非常量引用临时对象并延长其生命周期?

了解生命周期:最大生命周期和“静态”

从 C 扩展跟踪 CPython 对象的生命周期

本周小贴士#107:引用生命周期的扩展