可以返回一个括起来的封闭初始化器导致C++中的副本吗?

Joh*_*nes 7 c++ initializer-list return-value-optimization c++11

例:

struct s { int a; };

s func() { return {42}; }

int main() {
    s new_obj = func(); // line 6
    (void) new_obj;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这有效.现在,如果我们假设我们的编译器没有RVO会发生什么?

  1. func返回一个struct的结构s,因此{42}必须转换为s,然后返回并最终复制到new_obj第6行.
  2. func 返回初始化列表,因此无法进行深层复制.

语言是什么意思?你能举一个证明吗?

注意:我知道这在这个例子中看起来没用,但是对于返回非常大的常量std::arrays,我不想依赖RVO.

Cas*_*eri 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;
}
Run Code Online (Sandbox Code Playgroud)

当使用gcc 4.8.1编译-fno-elide-constructors以防止RVO输出时

move
Run Code Online (Sandbox Code Playgroud)

如果在f返回语句中没有使用花括号,那么输出就是

move
move
Run Code Online (Sandbox Code Playgroud)

没有RVO,会发生以下情况.f必须创建一个类型的临时对象foo,让我们调用它ret来返回.

如果return { 42 };被使用,则ret直接从值初始化42.因此到目前为止还没有调用复制/移动构造函数.

如果return 42;使用,那么另一个临时,让我们称之为tmp直接初始化42tmp移动到创建ret.因此,到目前为止,一个移动构造函数被调用.(注意,这tmp是一个右值并且foo有一个移动构造函数.如果没有移动构造函数,那么将调用复制构造函数.)

现在ret是一个rvalue,用于初始化obj.因此,移动构造器被调用以移动retobj.(同样,在某些情况下,可以调用复制构造函数.)因此,一个(for return { 42 };)或两个(for return 42;)移动发生.

正如我在对OP的问题的评论中所说,这篇文章非常相关: 即使XZY具有非复制约束,构造助手make_XYZ也允许RVO和类型推导.尤其是外观极好答案 R.费尔南德斯Martinho.

  • 在braced-init-lists的情况下,`ret`是_copy-list-initialized_!如果从`int`到`foo`的转换构造函数被声明为`explicit`,则会出现错误.同样在`return 42;`的情况下.它总是_copy-initialization_. (2认同)