23

右辺値を const 左辺値参照に割り当てると、一時的な有効期間がスコープの終わりまで延長されるという事実を認識しています。ただし、いつこれを使用し、いつ戻り値の最適化に頼るべきかは明確ではありません。

LargeObject lofactory( ... ) {
     // construct a LargeObject in a way that is OK for RVO/NRVO
}

int main() {
    const LargeObject& mylo1 = lofactory( ... ); // using const&
    LargeObject mylo2 = lofactory( ... ); // same as above because of RVO/NRVO ?
}

Scot Meyers の「より効果的な C++ (項目 20)」によると、2 番目の方法はコンパイラによって最適化され、オブジェクトをその場で構築できます (これは理想的でありconst&、最初の方法で達成しようとするものとまったく同じです)。

  1. const&一時的に使用する場合、および RVO/NRVO に依存する場合に、一般的に受け入れられているルールまたはベスト プラクティスはありますか?
  2. const&メソッドを使用しないよりも使用する方が悪い状況はありますか? (たとえば、C++ 11の移動セマンティクスが実装されている場合について考えLargeObjectています...)
4

2 に答える 2

17

最も単純なケースを考えてみましょう:

lofactory( ... ).some_method();

この場合、lofactoryから caller コンテキストへの 1 つのコピーが可能ですが、RVO/NRVOによって最適化して取り除くことができます。


LargeObject mylo2 ( lofactory( ... ) );

この場合、可能なコピーは次のとおりです。

  1. lofactoryから呼び出し元のコンテキストに一時的に戻ります – RVO/NRVOによって最適化できます
  2. mylo2一時的なものからコピー構築します – コピー省略によって最適化できます

const LargeObject& mylo1 = lofactory( ... );

この場合、まだ 1 つのコピーが可能です。

  1. lofactoryからcaller コンテキストに一時的に戻ります – RVO/NRVOによって最適化できます(あまりにも!)

参照はこの一時にバインドされます。


そう、

const& を一時的に使用する場合、および RVO/NRVO に依存する場合に、一般的に受け入れられているルールまたはベスト プラクティスはありますか?

上で述べたように、 の場合でもconst&、不要なコピーが可能であり、RVO/NRVOによって最適化して削除できます。

コンパイラがRVO/NVROを適用する場合、ほとんどの場合、ステージ 2 (上記) でコピー省略が行われます。その場合、コピー省略は NRVO よりもはるかに簡単だからです。

ただし、最悪の場合、const&ケース用に 1 つのコピーがあり、値を初期化するときに 2 つのコピーがあります。

const& メソッドを使用すると、使用しないよりも悪い状況が発生する可能性はありますか?

そのようなケースはないと思います。少なくとも、コンパイラがconst&. (同様の状況の例として、MSVC が集合体の初期化に NVRO を実行しないことに気付きました。)

(たとえば、LargeObjectにそれらが実装されている場合、C++ 11の移動セマンティクスについて考えています...)

C++11 では、ifLargeObjectに移動セマンティクスがある場合、最悪の場合、そのconst&ケースでは 1 つの移動があり、値を初期化すると 2 つの移動があります。だから、const&まだ少し良いです。


したがって、コンパイラが何らかの理由でコピー省略を実行できなかった場合にコピーが妨げられる可能性があるため、可能な場合は常に一時変数を const& にバインドすることをお勧めします。

アプリケーションの実際のコンテキストを知らなくても、これは良いルールのように思えます。

C++11 では、一時的に右辺値参照 (LargeObject&&) にバインドできます。したがって、そのような一時的なものは変更できます。


ちなみに、Move セマンティック エミュレーションは、さまざまなトリックによって C++98/03 で利用できます。例えば:

ただし、移動セマンティックが存在する場合でも、安価に移動できないオブジェクトがあります。たとえば、内部に double data[4][4] を持つ 4x4 行列クラス。そのため、コピー省略 RVO/NRVO は、C++11 でも依然として非常に重要です。ちなみに、Copy-elision/RVO/NRVO が発生すると、Move よりも高速です。


PS、実際のケースでは、考慮すべき追加事項がいくつかあります。

たとえば、ベクトルを返す関数がある場合、Move/RVO/NRVO/Copy-Elision が適用されても、100% 効率的ではない可能性があります。たとえば、次のケースを考えてみましょう。

while(/*...*/)
{
    vector<some> v = produce_next(/* ... */); // Move/RVO/NRVO are applied
    // ...
}

コードを次のように変更すると、より効率的になります。

vector<some> v;
while(/*...*/)
{
    v.clear();

    produce_next( v ); // fill v
    // or something like:
    produce_next( back_inserter(v) );
    // ...
}

この場合、 v.capacity() が十分な場合、ベクター内で既に割り当てられているメモリを再利用できるため、反復ごとに Produce_next 内で新しい割り当てを行う必要はありません。

于 2012-11-10T02:14:26.247 に答える
6

lofactory次のようにクラスを作成する場合:

LargeObject lofactory( ... ) {
    // figure out constructor arguments to build a large object
    return { arg1, arg2, arg3 }  //  return statement with a braced-init-list
}

この場合、RVO / NRVOはなく、直接構築されます。標準のセクション6.6.3には、「braced-init-listreturnを含むステートメントは、指定された初期化子リストからのcopy-list-initialization(8.5.4)によって関数から返されるオブジェクトまたは参照を初期化します。」</p >>

次に、オブジェクトをキャプチャする場合

LargeObject&& mylo = lofactory( ... ); 

コピーはありません。ライフタイムは期待どおりになり、myloを変更できます。

そして、すべてどこにもコピーがなく、保証されています。

于 2012-11-17T06:36:44.540 に答える