示例:
struct s { int a; };
s func() { return {42}; }
int main() {
s new_obj = func(); // line 6
(void) new_obj;
return 0;
}
这很有效。现在,如果我们假设我们的编译器没有RVO会发生什么呢?
func
会返回s
的结构,因此{42}
必须转换为s
,然后返回并最终复制到第6行的new_obj
。 func
会返回初始化列表,因此无法进行深层复制。语言是什么意思?你能举一个证明吗?
注意:我知道这在这个例子中似乎没什么用,但是对于返回非常大的,常量大小的std::array
,我不想依赖RVO。
答案 0 :(得分:5)
考虑以下示例:
#include <iostream>
struct foo {
foo(int) {}
foo(const foo&) { std::cout << "copy\n"; }
foo(foo&&) { std::cout << "move\n"; }
};
foo f() {
//return 42;
return { 42 };
}
int main() {
foo obj = f();
(void) obj;
}
使用带有-fno-elide-constructors
的gcc 4.8.1进行编译时,防止RVO输出
move
如果在f
中使用了没有花括号的return语句,则输出为
move
move
没有RVO,会发生以下情况。 f
必须创建foo
类型的临时对象,让我们将其称为ret
,以便返回。
如果使用return { 42 };
,则ret
直接从值42
初始化。因此到目前为止还没有调用复制/移动构造函数。
如果使用的是return 42;
,那么另一个临时的,我们称之为tmp
是从42
直接初始化,tmp
被移动到创建ret
。因此,到目前为止,一个移动构造函数被调用。 (注意tmp
是一个右值,foo
有一个移动构造函数。如果没有移动构造函数,那么将调用复制构造函数。)
现在ret
是一个右值,用于初始化obj
。因此,调用移动构造器以从ret
移动到obj
。 (同样,在某些情况下,可以调用复制构造函数。)因此,任何一个(return { 42 };
)或两个(return 42;
)移动都会发生。
正如我在对OP的问题的评论中所说,这篇文章非常相关: construction helper make_XYZ allowing RVO and type deduction even if XZY has noncopy constraint。特别是answer的优秀 R. Martinho Fernandes。