10

まず、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() の実装の詳細はないと言えます。

しかし、上記をコンパイルして実行すると、RVO がここで動作していることを示す "Copy Ctor" 文字列が表示されませんでした。

「getFoo()」の実装の詳細が翻訳単位 main.o に公開されていなくても、これをどのように達成できるか教えていただければ幸いです。

GCC (g++) 4.4.6 を使用して上記の実験を行いました。

4

3 に答える 3

13

コンパイラは一貫して動作する必要があります。

つまり、コンパイラは戻り値の型だけを見て、その型に基づいて、その型のオブジェクトを返す関数がどのように値を返すかを決定する必要があります。

少なくとも典型的なケースでは、その決定はかなり簡単です。戻り値に使用するレジスタ (または 2 つ) を確保します (たとえば、Intel/AMD x86/x64 では通常 EAX または RAX になります)。そこに収まるほど小さい型はすべてそこに返されます。大きすぎてそこに収まらない型の場合、関数は、返された結果を格納する場所を示す隠しポインター/参照パラメーターを受け取ります。これは、RVO/NRVO がまったく関与しなくても多くの場合に適用されることに注意してください。実際、オブジェクトstructを返す C++ の場合と同様に、a を返す C コードにも同様に適用されます。classC でa を返すことはstructおそらく C++ ほど一般的ではありませんが、それでも許可されており、コンパイラはそれを行うコードをコンパイルできなければなりません。

実際には、削除できる別々の (可能性のある) コピーが 2 つあります。1 つは、コンパイラが戻り値となるローカルを保持するためにスタックにスペースを割り当て、そこから戻り時にポインタが参照する場所にコピーすることです。

2 つ目は、そのリターン アドレスから、実際に値を格納する必要がある別の場所へのコピーの可能性です。

最初のものは関数自体の内部で削除されますが、外部インターフェイスには影響しません。最終的には、隠しポインターが指示する場所にデータを配置します。唯一の問題は、最初にローカル コピーを作成するか、常にリターン ポイントを直接操作するかです。明らかに [N]RVO では、常に直接動作します。

2番目に考えられるコピーは、その(潜在的な)一時的なものから、値が実際に必要な場所へのコピーです。これは、関数自体ではなく、呼び出しシーケンスを最適化することによって排除されます。つまり、一時的な場所ではなく、その戻り値の最終的な宛先へのポインターを関数に与え、そこからコンパイラーが値をその宛先にコピーします。 .

于 2012-07-23T15:19:24.613 に答える
6

maingetFooRVO が発生するためにの実装の詳細は必要ありません。終了後に戻り値が何らかのレジスターにあることを単に期待していgetFooます。

getFooこれには 2 つのオプションがあります。スコープ内にオブジェクトを作成してからリターン レジスタにコピー (または移動) するか、そのレジスタにオブジェクトを直接作成します。これが起こることです。

メインに他の場所を見るように指示しているわけではなく、そうする必要もありません。リターンレジスタを直接使用するだけです。

于 2012-07-23T15:19:00.383 に答える
3

(N)RVO は翻訳単位とは無関係です。この用語は一般に、関数内 (ローカル変数から戻り値へ) と呼び出し元 (戻り値からローカル変数へ) に適用できる 2 つの異なるコピー省略を指すために使用され、それらについて説明する必要があります。別々に。

適切な RVO

これは関数内で厳密に実行されます。次のことを考慮してください。

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

概念的には、2 つのオブジェクトlocalと、返されるオブジェクトがあります。コンパイラは関数をローカルに分析し、両方のオブジェクトの有効期間がバインドされていることを判断できlocalます。返された値へのコピーのソースとして機能するためだけに存続します。その後、コンパイラは両方の変数を 1 つの変数にバインドして使用できます。

呼び出し側で省略をコピー

呼び出し側では、 を検討してT x = foo();ください。ここでも、 と から返されたオブジェクトの 2 つのオブジェクトがありfoo()ますx。また、コンパイラは有効期間がバインドされていることを判断し、両方のオブジェクトを同じ場所に配置できます。

さらに読む:

于 2012-07-23T15:24:10.027 に答える