代码之家  ›  专栏  ›  技术社区  ›  HolyBlackCat

当强制性RVO应用于延长临时使用寿命的引用时会发生什么?

  •  1
  • HolyBlackCat  · 技术社区  · 3 年前

    当一个引用被另一个扩展临时生存期的引用初始化时,这个新引用不会扩展任何内容。

    但是,当强制性RVO阻止引用被复制时,会发生什么?

    考虑这个例子: run on gcc.godbolt.org

    #include <iostream>
    
    struct A
    {
        A() {std::cout << "A()\n";}
        A(const A &) = delete;
        A &operator=(const A &) = delete;
        ~A() {std::cout << "~A()\n";}
    };
    
    struct B
    {
        const A &a;
    };
    
    struct C
    {
        B b;
    };
    
    int main()
    {
        [[maybe_unused]] C c{ B{ A{} } };
        std::cout << "---\n";
    }
    

    根据GCC打印

    A()
    ---
    ~A()
    

    但在Clang的领导下,结果是

    A()
    ~A()
    ---
    

    哪个编译器是正确的?

    乍一看,海湾合作委员会做了正确的事情。但在这个例子中:

    C foo()
    {
        return { B{ A{} } };
    }
    
    int main()
    {
        [[maybe_unused]] C c = foo();
        std::cout << "---\n";
    }
    

    的寿命 A 当然不能扩展到函数之外(两个编译器都同意这一点)。

    既然这个片段应该与第一个片段具有相同的RVO,那么行为不应该是相同的吗?因此,Clang的行为似乎更加一致。

    0 回复  |  直到 2 年前
        1
  •  10
  •   dumbass    3 年前

    GCC是对的。

    在第二个例子中,我们没有寿命延长,因为 [class.temporary] ¶6.11 :

    临时绑定到函数中返回值的生存期 return 陈述 [stmt.return] )未扩展;临时在中的完整表达式末尾被销毁 回来 陈述

    如果我们这样重写示例:

    C foo(const A &a)
    {
        return { B{ a } };
    }
    
    int main()
    {
        C c = foo(A {});
        std::cout << "---" << std::endl;
    }
    

    clause 6.9 反而会起作用:

    绑定到函数调用中引用参数的临时对象( [expr.call] )一直持续到包含调用的完整表达式完成。

    那么,为什么寿命延长适用于第一个例子?很简单:聚合初始化程序不是函数调用。它们在标准的不同部分进行了描述:函数调用在中进行了描述 [expr.call] ,而初始化表达式在中进行了描述 [expr.type.conv] (和中的聚合初始化 [dcl.init.aggr] ).

    但是,请注意,如果 B 有一个实际的构造函数:

    struct B
    {
        const A &a;
        B(const A &a_): a(a_) {}
    };
    

    那么调用该构造函数就算作函数调用,此时 [class.temporary] ¶6.9 再次变得相关。 0 如果没有它,就寿命而言,聚合的引用成员将被视为直接声明为变量。

    如果你想像Clang(不正确地)那样在没有临时寿命延长的情况下执行聚合初始化,你可以使用括号而不是大括号进行初始化,这将触发 [class.temporary] ¶6.10 :

    绑定到类类型聚合的引用元素的临时对象,该聚合是从带括号的初始化的 表达式列表 ( [dcl.init] )一直持续到完成包含的完整表达式 表达式列表 .

    不幸的是,Clang目前显然没有实现这一点,因为这是C++20的一个新添加(提案 P0960 ). 请注意,该提案的文本甚至明确指出,第一个例子中的GC行为是标准的意图。


    0 大概该子句仅提及中所述的函数调用 [expr.call] ,我很难在标准中找到任何明确的语句,即构造函数调用应该以相同的方式工作。

    推荐文章