6

例えば:

void f(T&& t); // probably making a copy of t

void g() 
{
    T t;
    // do something with t
    f(std::move(t));
    // probably something else not using "t"
}

void f(T const& t)この場合、優れたコンパイラは同じコードを生成するため、同等ですか?これが重要な場合は、>=VC10および>=GCC4.6に興味があります。

編集:

回答に基づいて、質問を少し詳しく説明したいと思います。

比較rvalue-referenceしてpass-by-valueアプローチすると、での使用を忘れがちstd::moveですpass-by-value。コンパイラは、変数にこれ以上変更が加えられていないことを確認し、不要なコピーを削除できますか?

rvalue-referenceこのアプローチでは、最適化されたバージョンのみが「暗黙的」になります。たとえば、ユーザーがインスタンスを使用していない場合はf(T())、他のケースを明示的に指定するf(std::move(t))か、明示的にコピーを作成する必要があります。それで、この最適化に関する光の中で、アプローチは良いと見なされますか?f(T(t));trvalue-reference

4

3 に答える 3

5

それは間違いなく同じではありません。一度は右辺値にのみバインドできますが、T &&右辺値と左辺値の両方にバインドできます。第二に、移動の最適化を許可しません。「おそらくコピーを作成したい」場合は、実際にの移動コピーを作成できます。これは、より効率的である可能性があります。T const &T const &tT &&t

例:

void foo(std::string const & s) { std::string local(s); /* ... */ }

int main()
{
    std::string a("hello");
    foo(a);
}

このコードでは、を含む文字列バッファ"hello"が2回存在する必要があります。1回は。の本体にmain、もう1回は。の本体に存在しfooます。対照的に、右辺値参照とを使用した場合はstd::move(a)、まったく同じ文字列バッファを「移動」でき、一度だけ割り当てて設定する必要があります。

@Alonが指摘しているように、正しいイディオムは実際には値渡しです

void foo(std::string local) { /* same as above */ }

int main()
{
    std::string a("hello");
    foo(std::move(a));
}
于 2013-03-25T11:55:36.923 に答える
2

まあ、それはfがtで何をするかに依存します、もしそれがそれのコピーを作成するなら、私はこれをやるのに長々と行くでしょう:

void f(T t) // probably making a copy of t
{
    m_newT = std::move(t); // save it to a member or take the resources if it is a c'tor..
}

void g() 
{
    T t;
    // do something with t
    f(std::move(t));
    // probably something else not using "t"
}

次に、move c'torsの最適化を許可し、どのような場合でも't'リソースを取得します。また、関数に'moved'された場合は、関数に移動することの非コピーを取得します。動かされなかったので、おそらく1つのコピーが必要でした

後でコードに含めると、次のようになります。

f(T());

次に、fユーザーが知らないうちに自由移動の最適化を行います。

引用に注意してください:「この場合、優れたコンパイラは同じコードを生成するため、void f(T const&t)は同等ですか?」

「ポインタ」のみが転送され、c'torはまったく呼び出されず、移動も他の何も行われないため、同等ではなく、作業が少なくなります。

于 2013-03-25T11:44:06.203 に答える
1

左辺値参照の取得と右辺値参照の取得constは、2つの異なるものです。

類似点:

  • どちらも参照であるため、コピーや移動は発生しません。参照はオブジェクトを参照するだけであり、オブジェクトをコピー/移動することはありません。

違い:

  • 左辺値参照は、constすべて(左辺値または右辺値)にバインドされます。右辺値参照は非右辺値にのみバインドさconstれます-はるかに制限されています。

  • const関数内のパラメーターは、左辺値参照の場合は変更できません。右辺値参照の場合は変更できます(非値であるためconst)。

いくつかの例を見てみましょう:

  1. const左辺値の参照を取る:void f(const T& t);

    1. 左辺値を渡す:

      T t; f(t);
      

      tこれはオブジェクトの名前であるため、左辺値式です。左辺値参照はconst何にでもバインドできるためt、参照によって渡されます。何もコピーされず、何も移動されません。

    2. 右辺値を渡す:

      f(T());
      

      これT()は一時オブジェクトを作成するため、右辺値式です。繰り返しますが、const左辺値参照は何にでもバインドできるので、これは問題ありません。何もコピーされず、何も移動されません。

    どちらの場合も、t関数の内部は渡されたオブジェクトへの参照です。参照isによって変更することはできませんconst

  2. 右辺値の参照を取る: `void f(T && t);

    1. 左辺値を渡す:

      T t;
      f(t);
      

      これにより、コンパイラエラーが発生します。右辺値参照は左辺値にバインドされません。

    2. 右辺値を渡す:

      f(T());
      

      右辺値参照は右辺値にバインドできるため、これは問題ありません。関数内の参照tは、によって作成された一時オブジェクトを参照しますT()

では、考えてみましょうstd::move。まず最初に:std::move実際には何も動かしません。アイデアは、それに左辺値を与えて、それを右辺値に変えるというものです。それがすべてです。したがってf、右辺値参照を取得する場合は、次のようにすることができます。

T t;
f(std::move(t));

これtは、左辺値ですstd::move(t)が、右辺値であるために機能します。これで、右辺値参照をバインドできます。

では、なぜ右辺値参照引数を取るのでしょうか。実際、移動コンストラクターと代入演算子を定義する場合を除いて、頻繁に行う必要はありません。const右辺値参照を受け取る関数を定義するときはいつでも、ほぼ確実に左辺値参照のオーバーロードを与えたいと思うでしょう。ほとんどの場合、ペアで提供する必要があります。

void f(const T&);
void f(T&&);

この関数のペアが役立つのはなぜですか?最初の値は、左辺値(またはconst右辺値)を指定するたびに呼び出され、2番目の値は、変更可能な右辺値を指定するたびに呼び出されます。通常、右辺値を受け取るということは、一時オブジェクトが与えられたことを意味します。これは、オブジェクトが長く存在しないことがわかっているという事実に基づいて、その内部を破壊し、最適化を実行できることを意味するため、すばらしいニュースです。

したがって、この関数のペアを使用すると、一時オブジェクトを取得していることがわかっているときに最適化を行うことができます。

この関数のペアの非常に一般的な例があります。コピーコンストラクターと移動コンストラクターです。それらは通常、次のように定義されます。

T::T(const T&); // Copy constructor
T::T(T&&); // Move constructor

したがって、移動コンストラクターは、実際には、一時オブジェクトを受け取るときに最適化された単なるコピーコンストラクターです。

もちろん、渡されるオブジェクトは必ずしも一時的なオブジェクトではありません。上で示したように、を使用std::moveして左辺値を右辺値に変換できます。次に、それは関数の一時オブジェクトのように見えます。基本的に使用std::moveすると、「このオブジェクトを一時オブジェクトとして扱うことができます」と言います。それが実際に移動するかどうかは関係ありません。

ただし、コピーコンストラクターと移動コンストラクターを作成する以外に、この関数のペアを使用するのには十分な理由があります。オブジェクトを受け取り、一時オブジェクトであるかどうかに関係なく、オブジェクトとまったく同じように動作する関数を作成している場合は、そのオブジェクトを値で取得するだけです。検討:

void f(T t);

T t;
f(t);
f(T());

への最初の呼び出しfでは、左辺値を渡します。それが関数にコピーされます。への2番目の呼び出しfでは、右辺値を渡します。そのオブジェクトは関数に移動されます。参照してください-オブジェクトを効率的に移動させるために右辺値参照を使用する必要さえありませんでした。私たちはそれを価値あるものとしてとらえました!なんで?コピー/移動を行うために使用されるコンストラクターは、式が左辺値であるか右辺値であるかに基づいて選択されるためです。コピー/移動コンストラクターに任せてください。

異なる引数タイプが同じコードになるかどうかについては、まったく別の質問です。コンパイラは、as-ifルールの下で動作します。これは単に、プログラムが標準の指示どおりに動作する限り、コンパイラーが好きなコードを出力できることを意味します。したがって、関数がまったく同じことを行う場合、関数は同じコードを出力する可能性があります。またはそうでないかもしれません。ただし、定数左辺値参照と右辺値参照を受け取る関数が同じことをしている場合は、悪い兆候です。

于 2013-03-25T13:43:27.937 に答える