考虑以下(用 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
,这意味着foo
s 析构函数对返回的向量有影响。
在 MSVC14.1 中,输出是0
. 您可以通过更换线路强制输出是一样的gcc8.3Foo foo(ret);
与{Foo foo(ret);}
(即通过强制的范围)。
我不认为这是悬空引用未定义行为(因为ret
在之前声明foo
),而是认为这可能是 MSVC14.1 中的错误(如果是这样,我将创建错误报告)。有人有确切消息么?
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 时可以改变的行为。这些行为包括调用构造函数或析构函数的可观察到的副作用,就像你的例子一样。