当对自动变量的析构函数调用修改函数返回值时,在 MSVC14.1 和 gcc8.3 之间观察到的差异

Bat*_*eba 36 c++ c++14

考虑以下(用 C++14 编译)

#include <iostream>
#include <vector>

// Foo adds an element to a std::vector passed by reference
// on construction in the destructor
struct Foo {
    Foo(std::vector<double>& v) : m_v(v){
    }
    ~Foo(){
        m_v.push_back(1.0);
    }
    std::vector<double>& m_v;
};

std::vector<double> bar(){
    std::vector<double> ret;
    Foo foo(ret);
    return ret;
}

int main(){
    std::cout << bar().size() << "\n";
}
Run Code Online (Sandbox Code Playgroud)

在 gcc8.3 中,输出是1,这意味着foos 析构函数对返回的向量有影响。

在 MSVC14.1 中,输出是0. 您可以通过更换线路强制输出是一样的gcc8.3Foo foo(ret);{Foo foo(ret);}(即通过强制的范围)。

我不认为这是悬空引用未定义行为(因为ret在之前声明foo),而是认为这可能是 MSVC14.1 中的错误(如果是这样,我将创建错误报告)。有人有确切消息么?

https://ideone.com/pnxWuJ

120*_*arm 21

我认为 C++20 标准的相关部分是 [stmt.return],它说

调用结果的复制初始化在 return 语句的操作数建立的完整表达式末尾的临时变量销毁之前进行排序,反过来,在局部变量销毁之前排序 (8.7)包含 return 语句的块

所以函数调用的结果(返回值)应该先被构造,然后foo被销毁。由于返回值是在foo的析构函数运行之前构造的,因此结果应该是 0。

[class.copy.elision] 部分说

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数有副作用。

所以两个编译器都可以被认为是正确的。


Gui*_*cot 16

我认为两个编译器都是对的。NRVO 是一种常见且允许的优化。如果确实发生了这种优化,您将看到析构函数插入其中的项目。

如果 NRVO 没有发生,那么就像@1201ProgramAlarm 所说的那样,预期的行为是返回空向量。

引用与他的回答相同的内容,

调用结果的复制初始化在 return 语句的操作数建立的完整表达式末尾的临时变量销毁之前进行排序,反过来,在局部变量销毁之前排序 (8.7)包含 return 语句的块

因此,在没有复制省略的情况下,您最终会在销毁之前复制向量,从而导致大小为0. 使用复制省略,您将插入到您在 main 函数中看到的相同向量中。

TL;DR:不要依赖应用 NRVO 时可以改变的行为。这些行为包括调用构造函数或析构函数的可观察到的副作用,就像你的例子一样。