如果没有定义析构函数,为什么返回值优化不会发生?

Ben*_*n C 5 c++ class copy-elision rvo nrvo

我希望从这个测试程序中看到命名返回值优化 (NRVO) 的复制省略,但它的输出是“地址不匹配!” 所以 NRVO 没有发生。为什么是这样?

// test.cpp
// Compile using:
//      g++ -Wall -std=c++17 -o test test.cpp
#include <string>
#include <iostream>

void *addr = NULL;

class A
{
public:
    int i;
    int j;

#if 0
    ~A() {}
#endif
};

A fn()
{
    A fn_a;

    addr = &fn_a;

    return fn_a;
}

int main()
{
    A a = fn();

    if (addr == &a)
        std::cout << "Addresses match!\n";
    else
        std::cout << "Addresses do not match!\n";
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. 如果通过启用#if上述定义析构函数,则 NRVO 确实会发生(并且在其他一些情况下也会发生,例如定义虚拟方法或添加std::string成员)。

  2. 没有定义任何方法,所以 A 是一个 POD 结构,或者在最近的术语中是一个平凡的类。我在上面的链接中没有看到明确的排除。

  3. 添加编译器优化(对于一个更复杂的示例,它不仅仅是简化为空程序!)没有任何区别。

  4. 综观组件用于第二实施例表明,这甚至发生在我期望强制返回值优化(RVO),因此上述NRVO未通过取的地址防止fn_afn()。x86-64 上的 Clang、GCC、ICC 和 MSVC 显示相同的行为,表明此行为是有意的,而不是特定编译器中的错误。

     class A
     {
     public:
         int i;
         int j;
    
     #if 0
         ~A() {}
     #endif
     };
    
     A fn()
     {
         return A();
     }
    
     int main()
     {
         // Where NRVO occurs the call to fn() is preceded on x86-64 by a move
         // to RDI, otherwise it is followed by a move from RAX.
         A a = fn();
     }
    
    Run Code Online (Sandbox Code Playgroud)

eer*_*ika 4

在返回纯右值(第二个示例)的情况下允许这样做的语言规则是:

\n
\n

[类.临时]

\n

当类类型 X 的对象传递给函数或从函数返回时,如果 X 至少有一个合格的复制或移动构造函数([特殊]),则每个此类构造函数都是平凡的,并且 X 的析构函数是平凡的或删除的,允许实现创建一个临时对象来保存函数参数或结果对象。\n临时对象分别从函数参数或返回值构造,并且函数的参数或返回对象被初始化,就像使用合格的普通构造函数一样复制临时对象(即使该构造函数不可访问或不会被重载决策选择来执行对象的复制或移动)。\n[注意:授予此自由度是为了允许将类类型的对象传递给或从寄存器中的函数返回。\n\xe2\x80\x94 尾注\n]

\n
\n
\n
\n

为什么[在某些情况下]没有发生返回值优化?

\n
\n

该规则的动机在所引用规则的注释中进行了解释。本质上,RVO 有时比没有 RVO 效率低。

\n
\n

如果通过启用上面的 #if 来定义析构函数,那么 RVO 确实会发生(并且在其他一些情况下也会发生,例如定义虚拟方法或添加 std::string 成员)。

\n
\n

在第二种情况下,规则对此进行了解释,因为只有当析构函数很简单时才允许创建临时对象。

\n

在 NRVO 的情况下,我认为这取决于语言的实现。

\n