25

[C ++ 11:12.8 / 31]に記載されています:

コピーの省略と呼ばれるこのコピー/移動操作の省略は許可されています[...]:

—クラスreturn型を持つ関数のreturnステートメントで、式が、関数return型と同じcv-unqualified型を持つ不揮発性自動オブジェクト(関数またはcatch-clauseパラメーター以外)の名前である場合、自動オブジェクトを関数の戻り値に直接構築することにより、コピー/移動操作を省略できます。

これは、

#include <iostream>

using namespace std;

struct X
{
    X() { }
    X(const X& other) { cout << "X(const X& other)" << endl; }
};

X no_rvo(X x) {
    cout << "no_rvo" << endl;
    return x;
}

int main() {
    X x_orig;
    X x_copy = no_rvo(x_orig);

    return 0;
}

印刷します

X(const X& other)
no_rvo
X(const X& other)

2番目のコピーコンストラクタが必要なのはなぜですか?コンパイラは単にxの寿命を延ばすことはできませんか?

4

2 に答える 2

12

Imagineは、コンパイラがコンパイル時に宣言のみを表示するようno_rvoに、とは異なるファイルで定義されています。mainmain

X no_rvo(X x);

X返される型のオブジェクトが引数と何らかの関係があるかどうかはわかりません。その時点でそれが知っていることから、の実装no_rvo

X no_rvo(X x) { X other; return other; }

したがって、たとえば行をコンパイルするとき

X const& x = no_rvo(X());

最大限に最適化すると、次のようになります。

  • no_rvo引数として渡される一時Xを生成します
  • を呼び出しno_rvo、その戻り値をにバインドしますx
  • 渡された一時オブジェクトを破棄no_rvoします。

ここで、からの戻り値no_rvoが渡されたオブジェクトと同じオブジェクトである場合、一時オブジェクトの破棄は、返されたオブジェクトの破棄を意味します。ただし、返されるオブジェクトは参照にバインドされているため、そのステートメントの有効期間を超えて存続期間が延長されるため、これは誤りです。ただし、引数を破棄しないことも解決策ではありません。これは、の定義no_rvoが上記の代替実装である場合は間違っているためです。したがって、関数が引数を戻り値として再利用できる場合、コンパイラーが正しい動作を判別できない状況が発生する可能性があります。

一般的な実装では、コンパイラはとにかくそれを最適化することができないことに注意してください。したがって、正式に許可されていないほど大きな損失ではありません。また、コンパイラー、これが観察可能な動作の変更につながらないことを証明できる場合は、とにかくコピーを最適化することが許可されていることに注意してください(いわゆるas-ifルール)。

于 2012-02-25T15:00:46.420 に答える
5

RVOの通常の実装では、呼び出し元のコードが、関数が結果オブジェクトを構築する必要があるメモリチャンクのアドレスを渡します。

関数の結果が正式な引数ではない自動変数である場合、そのローカル変数は呼び出し元が提供するメモリチャンクに配置するだけで、returnステートメントはコピーをまったく行いません。

値によって渡される引数の場合、呼び出し元のマシンコードは、関数にジャンプする前に、実際の引数を仮引数の場所にコピー初期化する必要があります。関数がその結果をそこに配置するには、最初に仮引数オブジェクトを破棄する必要があります。これには、いくつかのトリッキーな特殊なケースがあります(たとえば、その構文が直接または間接的に仮引数オブジェクトを参照する場合)。したがって、結果の場所を仮引数の場所で識別する代わりに、ここでの最適化では、関数の結果に対して、呼び出された別のメモリチャンクを論理的に使用する必要があります。

ただし、レジスタに渡されない関数の結果は、通常、呼び出し元によって提供されます。returnつまり、正式な議論を表す式の場合、一種の減少したRVOであるRVOとして合理的に話すことができるのは、とにかく起こることです。また、「自動オブジェクトを関数の戻り値に直接構築することによって」というテキストには適合しません。

要約すると、呼び出し元が値を渡す必要があるデータフローは、関数ではなく、仮引数のストレージを初期化するのは必ず呼び出し元であることを意味します。したがって、正式な引数からコピーバックすることは一般的に避けられません(イタチの用語は、コンパイラが非常に特殊なことを実行できる特殊なケース、特にインラインマシンコードを対象としています)。ただし、他のローカル自動オブジェクトのストレージを初期化する関数であるため、RVOを実行しても問題ありません。

于 2012-02-25T14:53:22.063 に答える