编译器是否可以删除以下副本?

Eri*_*ius 14 c++ copy-elision

我仍然是一名新手程序员,我知道过早的优化很糟糕,但我也知道复制大量的东西也很糟糕.

我已经阅读了复制省略和它的同义词,但维基百科上的例子让我觉得复制省略只有在要完全构建的同时返回要返回的对象时才会发生.

那些像矢量这样的对象呢?当用作返回值时,它通常只在填充某些东西时才有意义.毕竟,可以手动实例化空矢量.

那么,它是否也适用于这样的情况?

简洁的风格:

vector<foo> bar(string baz)
{
    vector<foo> out;
    for (each letter in baz)
        out.push_back(someTable[letter]);

    return out;
}

int main()
{
     vector<foo> oof = bar("Hello World");
}
Run Code Online (Sandbox Code Playgroud)

我使用bar(矢量和输出,字符串文本)没有真正的麻烦,但上面的方式看起来更好,美观,并且意图.

Kon*_*lph 10

例如维基百科上的例子让我觉得只有在要完全构造的同时返回要返回的对象时才能进行复制省略.

这是误导(读:错误).问题是在所有代码路径中只返回一个对象,即只发生潜在返回对象的一个构造.

你的代码很好,任何现代编译器都可以忽略副本.

另一方面,以下代码可能会产生问题:

vector<int> foo() {
    vector<int> a;
    vector<int> b;
    // … fill both.
    bool c;
    std::cin >> c;
    if (c) return a; else return b;
}
Run Code Online (Sandbox Code Playgroud)

这里,编译器需要完全构造两个不同的对象,并且稍后才决定返回哪些对象,因此它必须复制一次,因为它不能直接在目标内存位置构造返回的对象.

  • 这个例子是通过简单优化BTW:`if(c){swap(a,b); 返回a;`IOW,不要担心你是否需要设计RVO的所有内容. (11认同)

Bjö*_*lex 5

没有什么能阻止编译器删除副本.这在12.8.15中定义:

[...]在下列情况下允许复制操作的省略(可以合并以消除多份副本):

[...]

  • 当一个未绑定到引用(12.2)的临时类对象被复制到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到省略副本的目标中来省略复制操作

如果它确实取决于编译器和您使用的设置.


CB *_*ley 5

vector罐头的两个隐含副本- 通常是 - 被消除.命名返回值优化可以消除return语句中return out;隐含的副本,oof并且允许删除复制初始化中隐含的临时值.

通过两种优化,构建的对象vector<foo> out;与对象相同oof.

使用诸如此类的人为测试用例,可以更轻松地测试正在执行哪些优化.

struct CopyMe
{
    CopyMe();
    CopyMe(const CopyMe& x);
    CopyMe& operator=(const CopyMe& x);

    char data[1024]; // give it some bulk
};

void Mutate(CopyMe&);

CopyMe fn()
{
    CopyMe x;
    Mutate(x);
    return x;
}

int main()
{
    CopyMe y = fn();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

声明了复制构造函数但未定义复制构造函数,因此无法内联和消除对它的调用.使用现在相对较旧的gcc 4.4进行编译,得到以下程序集-O3 -fno-inline(过滤到demangle C++名称并编辑以删除非代码).

fn():
        pushq   %rbx
        movq    %rdi, %rbx
        call    CopyMe::CopyMe()
        movq    %rbx, %rdi
        call    Mutate(CopyMe&)
        movq    %rbx, %rax
        popq    %rbx
        ret

main:
        subq    $1032, %rsp
        movq    %rsp, %rdi
        call    fn()
        xorl    %eax, %eax
        addq    $1032, %rsp
        ret
Run Code Online (Sandbox Code Playgroud)

可以看出,没有调用复制构造函数.实际上,gcc甚至可以执行这些优化-O0.您必须提供-fno-elide-constructors以关闭此行为; 如果你这样做,那么gcc会生成两个对复制构造函数的调用CopyMe- 一个在内部,一个在调用之外fn().

fn():
        movq    %rbx, -16(%rsp)
        movq    %rbp, -8(%rsp)
        subq    $1048, %rsp
        movq    %rdi, %rbx
        movq    %rsp, %rdi
        call    CopyMe::CopyMe()
        movq    %rsp, %rdi
        call    Mutate(CopyMe&)
        movq    %rsp, %rsi
        movq    %rbx, %rdi
        call    CopyMe::CopyMe(CopyMe const&)
        movq    %rbx, %rax
        movq    1040(%rsp), %rbp
        movq    1032(%rsp), %rbx
        addq    $1048, %rsp
        ret

main:
        pushq   %rbx
        subq    $2048, %rsp
        movq    %rsp, %rdi
        call    fn()
        leaq    1024(%rsp), %rdi
        movq    %rsp, %rsi
        call    CopyMe::CopyMe(CopyMe const&)
        xorl    %eax, %eax
        addq    $2048, %rsp
        popq    %rbx
        ret
Run Code Online (Sandbox Code Playgroud)