13

我仍然是一个新手程序员,我知道过早的优化是不好的,但我也知道复制大量的东西也是不好的。

我已经阅读了复制省略,它是同义词,但是例如 Wikipedia 上的示例让我觉得只有当要返回的对象在完全构造的同时返回时才会发生复制省略。

像向量这样的对象呢,通常只有在填充某些东西时才有意义,当用作返回值时。毕竟,空向量只能手动实例化。

那么,在这种情况下它是否也有效?

简洁的坏风格:

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");
}

我在使用 bar(vector & out, string text) 时没有真正的麻烦,但上述方式在美学和意图上看起来要好得多。

4

3 回答 3

10

例如,维基百科上的例子让我觉得只有当要返回的对象在完全构造的同时返回时才会发生复制省略。

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

您的代码很好,任何现代编译器都可以省略副本。

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

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

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

于 2011-05-26T13:27:29.610 回答
5

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

[...]在以下情况下允许省略复制操作(可以组合起来以消除多个副本):

[...]

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

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

于 2011-05-26T13:26:21.283 回答
4

罐头的两个隐含副本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;
}

复制构造函数已声明但未定义,因此无法内联和消除对它的调用。使用现在相对较旧的 gcc 4.4 进行编译会得到以下程序集-O3 -fno-inline(过滤以去除 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

可以看出,没有调用复制构造函数。事实上,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
于 2011-05-26T13:27:05.867 回答