g ++:如果涉及多个翻译单元,RVO如何工作

时间:2012-07-23 15:05:45

标签: c++ g++ rvo

首先请查看以下代码,其中包含2个翻译单元。

--- foo.h ---

class Foo
{
public:
    Foo();
    Foo(const Foo& rhs);
    void print() const;
private:
    std::string str_;
};

Foo getFoo();

--- foo.cpp ---
#include <iostream>

Foo::Foo() : str_("hello")
{
    std::cout << "Default Ctor" << std::endl;
}

Foo::Foo(const Foo& rhs) : str_(rhs.str_)
{
    std::cout << "Copy Ctor" << std::endl;
}

void Foo:print() const
{
    std::cout << "print [" << str_ << "]" << std:endl;
}

Foo getFoo()
{
    return Foo(); // Expecting RVO
}

--- main.cpp ---
#include "foo.h"

int main()
{
    Foo foo = getFoo();
    foo.print();
}

请确保foo.cpp和main.cpp是不同的翻译单元。所以根据我的理解,我们可以说翻译单元main.o(main.cpp)中没有getFoo()的实现细节。

但是,如果我们编译并执行上述操作,我就看不到“Copy Ctor”字符串,表明RVO在这里工作。

如果你们中的任何人都知道如何实现这一点,即使“getFoo()”的实现细节没有暴露给翻译单元main.o,我们将非常感激。

我使用GCC(g ++)4.4.6进行了上述实验。

3 个答案:

答案 0 :(得分:12)

编译器必须始终如一地工作。

换句话说,编译器必须只查看返回类型,并根据该类型决定返回该类型对象的函数将如何返回该值。

至少在一个典型的情况下,该决定相当是微不足道的。它留出一个寄存器(或可能两个)用于返回值(例如,在通常为EAX或RAX的Intel / AMD x86 / x64上)。任何足够小的类型都将返回到那里。对于任何太大而不适合的类型,函数将接收一个隐藏的指针/引用参数,告诉它存放返回结果的位置。请注意,如果没有RVO / NRVO,这很有用 - 实际上,它同样适用于返回struct的C代码,就像C ++返回class对象一样。尽管返回struct可能在C中并不像在C ++中那样常见,但它仍然是允许的,并且编译器必须能够编译执行它的代码。

实际上可以删除两个单独的(可能的)副本。一个是编译器可以在堆栈上为本地保留返回值的空间分配空间,然后从那里复制到返回期间指针所指的位置。

第二个是从该返回地址到可能最终需要值的其他位置的可能副本。

第一个在函数本身内被消除,但对其外部接口没有影响。它最终将数据放在隐藏指针所指示的位置 - 唯一的问题是它是先创建本地副本,还是始终直接使用返回点。显然,[N] RVO可以直接使用。

第二个可能的副本是从那个(潜在的)临时副本到最终需要的值。这通过优化调用序列而不是函数本身来消除 - 即,给函数指向该返回值的最终目的地,而不是指向某个临时位置,然后编译器将该值复制到其目标中

答案 1 :(得分:5)

main不需要发生getFoo的实施细节。它只是希望在getFoo退出后返回值在某个寄存器中。

getFoo有两个选项 - 在其范围内创建一个对象,然后将其复制(或移动)到返回寄存器,或直接在该寄存器中创建对象。发生了什么。

它并没有告诉主要看其他地方,也不需要。它只是直接使用返回寄存器。

答案 2 :(得分:3)

(N)RVO与翻译单元无关。该术语通常用于指代可以在函数内部(从局部变量到返回值)和调用者(从返回值到局部变量)应用的两个不同的复制元素,并且应该讨论它们分开。

适当的RVO

这严格在函数内部执行,请考虑:

T foo() {
   T local;
   // operate on local
   return local;
}

从概念上讲,有两个对象local和返回的对象。编译器可以在本地分析函数并确定两个对象的生命周期是否已绑定:local仅用作返回值的副本源。然后,编译器可以将两个变量绑定在一个变量中并使用它。

在来电方复制elision

在来电方,请考虑T x = foo();。同样有两个对象,即foo()x返回的对象。再次,编译器可以确定生命周期被绑定并将两个对象放在同一位置。

进一步阅读: