右辺値参照と左辺値参照の違いについて


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]

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

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




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

右辺値参照(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>`を用いるべき。）




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

右辺値参照(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>`を用いるべき。）

