66894


C++の右辺値参照について勉強しています。  
右辺値参照と左辺値参照の違いがよく分かりません。  
左辺値参照でしかできないこと、右辺値参照でしかできないことはありますでしょうか？  
例えば、このサイト（<https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html>）を参考に以下のようなコピーコンストラクタをlarge_classに追加しましたが、すでに定義されているムーブコンストラクタと全く同じ働きをしました。(Visual
Studioのデバッガでインスタンスの中身を確認)

[code]

    large_class(large_class& r)//&を減らしただけ
      {
        ptr = r.ptr;
        r.ptr = nullptr;
      }
    
[/code]

この結果を見るに、左辺値参照を引数にとっても、ムーブコンストラクタやムーブ代入演算子の働きができているように思います。  
現状右辺値参照を使用できることのメリットが、クラス内でのコピーコンストラクタとムーブコンストラクタの識別及びコピー代入演算子、ムーブ代入演算子の識別ができるということくらいしか分からないのですが他にあるのでしょうか？

ご回答お待ちしております。


CC BY-SA 4.0
2020-05-22T15:18:55.590
66893
2020-05-23T14:23:48.607
12492
question
c++ c c++11 プログラミング言語
右辺値参照と左辺値参照の違いについて


>
> 現状右辺値参照を使用できることのメリットが、クラス内でのコピーコンストラクタとムーブコンストラクタの識別及びコピー代入演算子、ムーブ代入演算子の識別ができるということくらいしか分からないのですが他にあるのでしょうか？

右辺値参照(rvalue
reference)の主目的は、まさに「コピーとムーブを明確に区別する」ことです。C++11より古い時代には左辺値参照(lvalue
reference)しか存在せず、ムーブを直接的には表現できませんでした。

「関数引数の完全転送(perfect
forwarding)」といった応用的な使い方もありますが、基本はコピー／ムーブを識別するための仕組みと解釈すべきです。

* * *

> この結果を見るに、左辺値参照を引数にとっても、ムーブコンストラクタやムーブ代入演算子の働きができているように思います。

あなたの解釈は半分正しく、半分間違っています。

変更されたコンストラクタ`large_class(large_class&)`が行う内部処理は、確かに元のムーブコンストラクタ`large_class(large_class&&)`が行っていた処理と同じです。コンストラクタ実装コードに手を入れていないので当然です。

一方で`large_class`利用者からみると、クラスの振る舞いが変わってしまっています。下記コード片では`a`から`b`へのコピーを期待しますが、コンストラクタを変更してしまうとムーブ相当の処理が行われるためもはや`a`は有効なデータを保持していません。

[code]

    large_class a;
    large_class b = a;
    // このタイミングで a はどうなっている？
    
[/code]

ソースコード上はコピーが行われるように見えますが、実際にはムーブ処理が行われてしまうという非常に使いずらい／バグの温床になるクラスが出来上がっています。古いC++標準ライブラリに存在した`std::auto_ptr<T>`は、まさにこのような動作をするスマートポインタでした。C++11で右辺値参照が採用されことで時代遅れとなり、C++標準ライブラリからも削除されています。（`std::unique_ptr<T>`を用いるべき。）


CC BY-SA 4.0
2020-05-22T16:18:20.627
66894
2020-05-22T16:27:16.440
2020-05-22T16:27:16.440
49
49
66893
answer


こんにちは。

私の理解ですが、

  * 右辺値参照は、右辺値にしかマッチしない。
  * 参照は、右辺値でも左辺値でもマッチする。
  * コピーコンストラクタは、ふつう const T & で定義する。

コピーコンストラクタを呼ぶ側の人から見ると、大事な変数を渡した場合、間違っても右辺値にマッチされることがないので、moveコンストラクタに渡って破壊される心配がない。他方でコピーコンストラクタは引数を改変することがあってはならず、それをはっきりさせるためにconst参照で受ける:

[code]

    class MyLargeClass {
    ...
       MyLargeClass(const MyLargeClass &reference); // copy constructor
       MyLargeClass(MyLargeClass &&donor); // move constructor
    
       MyLargeClass operator+(const MyLargeClass &rhs) const;
    ...
    };
    
    void some_function() {
      MyLargeClass a;
      MyLargeClass b(a); // 引数が左辺値なので&&にマッチせず、コピーコンストラクタが呼ばれる
    
      MyLargeClass c, d;
      MyLargeClass e( c + d ); // 引数は一時オブジェクトであり、右辺値。
                               // どっちにもマッチするがマッチ範囲が狭い&&が
                               // 優先され、moveコンストラクタが呼ばれる。
    }
    
[/code]

と理解しているので、呼び分けできていて、納得しています。

ここまで書いて、ご質問とかみあってない気がしたので、ご質問の核心になるべく即して書きます：

moveコンストラクタの機能を果たす（右辺から実体を横取りするような）コンストラクタは、constではない参照を受けるコンストラクタを書けば、確かに実装できますが、呼びわけがうまくいかないと思います。

例えばローカル変数で手元に持っているオブジェクトをコピーコンストラクタの引数に渡した場合を考えてみてください。自分で作ったローカル変数にはconstが課せられていないので、以下のコードでは、bを作るときにaが破壊されてしまいます。

[code]

    class MyLargeClass2 {
    ...
       MyLargeClass2(const MyLargeClass2 &reference); // (A)copy constructor
       MyLargeClass2(MyLargeClass2 &donor); // (B)move constructor ?
    
       MyLargeClass2 operator+(const MyLargeClass2 &rhs) const;
    ...
    };
    
    void some_function() {
      MyLargeClass2 a;
      MyLargeClass2 b(a); // aへの改変が許されてるので(B)が呼ばれてしまい、aが破壊される。
    
      MyLargeClass2 c, d;
      MyLargeClass2 e( c + d ); // (B)が呼ばれて、こちらは狙い通り。
    }
    
[/code]

これを防ぐには、右辺値に限ってマッチするような引数の型が必要で、それが右辺値参照だと理解しております。

＃説明が間違ってましたらご指摘歓迎いたします。


CC BY-SA 4.0
2020-05-23T14:23:48.607
66921
2020-05-23T14:23:48.607
40200
66893
answer