复制省略的条件是什么?

12

我想验证以下优化是否按预期起作用:

  • RVO
  • 命名的RVO
  • 通过值传递参数时的拷贝省略

所以我写了这个小程序:

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <vector>

struct Foo {
    Foo(std::size_t length, char value) : data(length, value) { }

    Foo(const Foo & rhs) : data(rhs.data) { std::cout << "*** COPY ***" << std::endl; }

    Foo & operator= (Foo rhs) {
        std::cout << "*** ASSIGNMENT ***" << std::endl;
        std::swap(data, rhs.data); // probably expensive, ignore this please
        return *this;
    }

    ~Foo() { }

    std::vector<char> data;
};

Foo TestRVO() { return Foo(512, 'r'); }

Foo TestNamedRVO() { Foo result(512, 'n'); return result; }

void PassByValue(Foo inFoo) {}

int main()
{
    std::cout << "\nTest RVO: " << std::endl;
    Foo rvo = TestRVO();

    std::cout << "\nTest named RVO: " << std::endl;
    Foo named_rvo = TestNamedRVO();

    std::cout << "\nTest PassByValue: " << std::endl;
    Foo foo(512, 'a');
    PassByValue(foo);

    std::cout << "\nTest assignment: " << std::endl;
    Foo f(512, 'f');
    Foo g(512, 'g');
    f = g;
}

我开启了优化选项进行编译:

$ g++ -o test -O3 main.cpp ; ./test

这是输出结果:

Test RVO: 

Test named RVO: 

Test PassByValue: 
*** COPY ***

Test assignment: 
*** COPY ***
*** ASSIGNMENT ***

根据输出,RVO和已命名的RVO符合预期。然而,在赋值运算符和调用PassByValue时,不执行拷贝省略操作。

用户定义的复制构造函数是否不允许拷贝省略?(我知道RVO明确允许,但我不知道通过值传递时是否允许拷贝省略。)有没有一种方法可以在不定义复制构造函数的情况下验证拷贝省略?


1
只是为了明确,(N)RVO 就是 复制省略。它们不是唯一的形式,但说你的例子表明没有执行复制省略是不准确的。 - Dennis Zickefoose
复制省略通常允许所有临时对象,但不允许命名或绑定到引用的对象。似乎gcc执行精确允许的部分。 - n. m.
@Dennis Zickefoose 谢谢,我已经修正了文本。 - StackedCrooked
2个回答

11
标准规定(第12.8.15段):
这种复制操作的省略在以下情况下是允许的(可以组合使用以消除多个副本):
- 在具有类返回类型的函数的返回语句中,当表达式是非易失性自动对象的名称,且具有与函数返回类型相同的cv-unqualified类型时,可以通过直接构造自动对象到函数返回值中来省略复制操作。 - 当未绑定到引用的临时类对象将被复制到具有相同的cv-unqualified类型的类对象时,可以通过直接构造临时对象到省略复制的目标中来省略复制操作。
在这里没有应用这两种情况,因此不允许省略。第一个情况很明显(没有返回)。第二个不允许,因为您传递的对象不是临时对象。
请注意,您的代码仍然可以正常运行,因为您必须创建该副本。要消除该副本,您必须使用C++0x的移动语义。

1
在C++11中,该部分编号变为§12.8/31,并且还有两种情况允许复制/移动省略,但这些情况仅与异常处理相关。 - kennytm

9

如果您使用复制构造函数,它无法被省略,因为调用后复制的对象仍然存在。

如果您尝试以下方式,则可能效果更好:

PassByValue(Foo(512, 'a')); 

所有的优化都是允许的,但并不是必需的,因此每个编译器可以自行决定它能够和愿意做什么。


优化确实是在这里执行的。这个例子和@Space_C0wb0y的答案让我明白了。 - StackedCrooked

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接