「C++の基礎 - 左辺値 と 左辺値参照」の版間の差分

編集の要約なし
 
(同じ利用者による、間の1版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
C言語では、<u>代入演算子=の左側にあるものを左辺値(lvalue)</u>、<u>右側(rvalue)にあるものを右辺値</u>として決められている。<br>
左辺値 (lvalue) は、C言語から存在する基本的な概念であり、メモリ上の特定の場所を持つオブジェクトを指す。<br>
C++では、<u>左辺値は名前を持つオブジェクト(&演算子でアドレスを取得できる)</u>、<u>右辺値は名前を持たない一時オブジェクト</u>のことである。<br>
C++では、左辺値参照 (lvalue reference) を使用して、オブジェクトへの別名を作成できる。<br>
<br>
ここでは、右辺値、左辺値、右辺値参照、左辺値参照、ムーブ、ユニバーサル参照、完全転送の意味を記載する。<br>
<br><br>
<br><br>


== 右辺値 (rvalue) ==
== 左辺値 (lvalue) ==
  定義:
==== 定義 ====
左辺値とは、メモリ上の特定のアドレスを持つオブジェクトを指す。<br>
名前を持ち、プログラム実行中に識別可能なエンティティである。<br>
<br>
==== 特徴 ====
左辺値は、以下に示す特徴を持つ。<br>
* 名前を持つ変数や、参照、デリファレンス演算子(*)を使用した式等が該当する。
* 代入演算子の左側に現れることができる。
* &演算子を使用して、そのアドレスを取得できる。
* プログラム実行中、一定期間メモリ上に存在し続ける。
* 識別可能であり、複数回参照できる。
<br>
==== 左辺値の例 ====
<syntaxhighlight lang="c++">
#include <iostream>
class MyClass
{
  public:
    int value;
    MyClass(int v) : value(v) {}
};
int globalVar = 100;  // グローバル変数は左辺値
int main()
{
    // 変数は左辺値
    int x = 10;
    int y = 20;
    // 配列要素は左辺値
    int arr[5] = {1, 2, 3, 4, 5};
    arr[2] = 30;
    // オブジェクトのメンバは左辺値
    MyClass obj(42);
    obj.value = 50;
    // ポインタのデリファレンスは左辺値
    int* ptr = &x;
    *ptr = 15;
   
   
  一時的な値やメモリ上の特定のアドレスを持たないオブジェクトを指す。
    // 配列名は左辺値
<br>
    int* arrPtr = arr;
  特徴:
   
    // 文字列リテラルは左辺値(特殊なケース)
    const char* str = "Hello"; // "Hello"は左辺値
   
   
  リテラル、一時オブジェクト、非参照の戻り値を持つ関数呼び出し等が該当する。
    // 関数は左辺値
    int (*funcPtr)() = globalVar; // 関数へのポインタ
   
   
通常、代入演算子の右側にのみ現れる。
    // すべての左辺値はアドレスを取得できる
&演算子を使用してアドレスを取得することはできない。(C++11以降の一部の例外を除く)
    int* xAddr = &x;
    int* yAddr = &y;
    int* arrElemAddr = &arr[0];
    int* objValueAddr = &(obj.value);
   
   
  例えば、メソッドの戻り値、式の結果等は右辺値である。
    return 0;
  }
</syntaxhighlight>
<br>
<br>
同じ式でも、コンテキストにより左辺値にも右辺値にもなり得る。<br>
==== 左辺値になるもの ====
また、右辺値参照 (&&) の導入により、右辺値の扱いが大きく変わり、ムーブセマンティクスが可能になった。<br>
以下に示すものは左辺値である。<br>
* 変数名
* 配列要素
* クラスのメンバ変数
* ポインタのデリファレンス
* 文字列リテラル
*: const char[]型
* 左辺値参照を返す関数呼び出し
* 前置インクリメント・デクリメント
*: ++x, --x
* 代入式
*: 例 : int x = y
<br>
<br>
C++11以降、右辺値の概念は「純粋な右辺値 (prvalue)」と「xvalue」に細分化された。<br>
==== 左辺値にならないもの ====
以下に示すものは左辺値ではなく、右辺値である。<br>
* 数値リテラル
*: 例 : 42, 3.14等
* 算術演算の結果
*: x + y
* 非参照を返す関数呼び出し
* 後置インクリメント・デクリメント
*: x++, x--
* 一時オブジェクト
<br><br>
 
== 左辺値参照 (lvalue reference) ==
==== 定義 ====
左辺値参照は、既存のオブジェクトへの別名 (エイリアス) である。<br>
C++98から存在する従来の参照型であり、<u>型名&</u> という記法で表現される。<br>
<br>
<br>
==== 基本的な構文 ====
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  class X;
  int x = 10;
int& ref = x;        // 左辺値参照の宣言と初期化
   
   
  int getValue();
  const int& cref = x;  // const左辺値参照
</syntaxhighlight>
<br>
==== 左辺値参照の特性 ====
左辺値参照は、以下に示す特性を持つ。<br>
* 必ず初期化時に束縛する必要がある。
* 1度束縛すると、別のオブジェクトに再束縛できない。
* 参照自体はメモリを持たない。(コンパイラによる最適化の対象)
* 参照を通じた操作は、束縛したオブジェクトへの操作と同じである。
* 左辺値参照は、左辺値のみを束縛できる。
* const左辺値参照は、左辺値と右辺値の両方を束縛できる。
<br>
==== 使用例 ====
<syntaxhighlight lang="c++">
#include <iostream>
   
   
  int main()
  int main()
  {
  {
     X();                 // クラスのコンストラクタは右辺値
     int x = 10;
     int z = getValue();  // getValue関数の戻り値は右辺値
    int& ref = x;  // 左辺値参照の作成
    std::cout << "x: " << x << std::endl;      // 10
    std::cout << "ref: " << ref << std::endl; // 10
    // 参照を通じた値の変更
    ref = 20;
     std::cout << "x after ref = 20: " << x << std::endl;  // 20
   
   
     int i = 1;               // 変数iは左辺値, 1は右辺値
     // 変数を通じた値の変更
     i + 1;                   // 式i + 1は右辺値
    x = 30;
     // int *ptr = &(5 + 3)// エラー: 右辺値のアドレスは取得できない
     std::cout << "ref after x = 30: " << ref << std::endl; // 30
     // 同じアドレスを指している
    std::cout << "Address of x: " << &x << std::endl;
    std::cout << "Address of ref: " << &ref << std::endl;
   
    return 0;
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
右辺値は左辺値に変換できないため、以下はコンパイルエラーとなる。<br>
==== 左辺値参照の束縛 ====
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
#include <iostream>
  int main()
  int main()
  {
  {
     int x;       // 変数xは左辺値
     int x = 10;
     (x + 1) = 9;  // コンパイルエラー
    int y = 20;
  }
  </syntaxhighlight>
    // 左辺値参照は左辺値を束縛できる
<br><br>
     int& ref1 = x;  // OK
 
   
== 左辺値 (lvalue) ==
    // 左辺値参照は右辺値を束縛できない
定義:
    // int& ref2 = 10;    // コンパイルエラー
    // int& ref3 = x + y; // コンパイルエラー
    // 参照の「代入」は、束縛先の値を変更する
    int& ref2 = y;
    ref1 = ref2;  // xの値がyの値になる(ref1がref2を指すわけではない)
   
   
  メモリ上の特定のアドレスを持つオブジェクトを指す。
    std::cout << "x: " << x << std::endl; // 20
<br>
    std::cout << "y: " << y << std::endl; // 20
  特徴:
   
   
名前を持つ変数や、参照、デリファレンス演算子(*)を使用した式等が該当する。
    // 参照を再束縛することはできない
代入演算子の左側に現れることができる。 (ただし、これは必須条件ではない)
    // ref1はxへの別名であり続ける
   
   
  &演算子を使用して、そのアドレスを取得できる。
    return 0;
  }
</syntaxhighlight>
<br>
<br>
例えば、変数に代入されている値は左辺値である。<br>
==== const左辺値参照 ====
const左辺値参照は、特別な性質を持つ。<br>
<br>
<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  class X;
  #include <iostream>
#include <string>
void printValue(const int& value)
{
    std::cout << "Value: " << value << std::endl;
}
void printString(const std::string& str)
{
    std::cout << "String: " << str << std::endl;
}
   
   
  int main()
  int main()
  {
  {
     int i;          // 変数iは左辺値
     int x = 10;
    X x;           // クラスのインスタンスは左辺値
   
   
     X *ptr_X = &x; // ポインタ変数は左辺値
     // const左辺値参照は左辺値を束縛できる
    const int& cref1 = x;
   
   
     int *ptr = &x;  // 変数xは左辺値であるため、アドレスを取得することができる
     // const左辺値参照は右辺値を束縛できる
     *ptr     = 20;  // 変数*ptrは左辺値
    const int& cref2 = 20;      // OK
    const int& cref3 = x + 5;  // OK
    // const左辺値参照を通じた値の変更はできない
    // cref1 = 30;  // コンパイルエラー
     // 元の変数を通じた値の変更は可能
     x = 30;
    std::cout << "cref1 after x = 30: " << cref1 << std::endl;  // 30
    // 関数の引数としてのconst左辺値参照
    printValue(x);              // 左辺値を渡す
    printValue(42);            // 右辺値を渡す
    printValue(x + 10);         // 式の結果(右辺値)を渡す
   
    std::string str = "Hello";
    printString(str);          // 左辺値を渡す
    printString("World");      // 右辺値を渡す
    return 0;
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br>
 
==== 一時オブジェクトの寿命延長 ====
== 右辺値と左辺値の比較 ==
const左辺値参照に束縛された一時オブジェクトは、参照のスコープが終了するまで寿命が延長される。<br>
右辺値と左辺値の比較を以下に示す。<br>
<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  // xは左辺値、1は右辺値
  #include <iostream>
  int x = 1;
  #include <string>
   
   
  // yは左辺値、式x + 1は右辺値、xは左辺値
  class TempObject
  int y = x + 1;
  {
public:
    TempObject() { std::cout << "TempObject created" << std::endl; }
    ~TempObject() { std::cout << "TempObject destroyed" << std::endl; }
   
   
// zは左辺値、func関数の戻り値は右辺値、式y + 2も右辺値
    void use() const { std::cout << "Using TempObject" << std::endl; }
  int z = func(y + 2);
  };
   
   
  struct Point
  TempObject createObject()
  {
  {
     int x = 0;
     return TempObject();
    int y = 0;
  }
  };
   
   
  // Pointクラスのインスタンスptは左辺値、Pointクラスのコンストラクタは右辺値
  int main()
  Point pt = Point();
  {
    std::cout << "--- Without reference ---" << std::endl;
    createObject().use(); // 一時オブジェクトはすぐに破棄される
   
   
  // 1は右辺値
    std::cout << "\n--- With const lvalue reference ---" << std::endl;
1;
    const TempObject& ref = createObject(); // 寿命が延長される
    ref.use();
    std::cout << "Still in scope" << std::endl;
    // スコープ終了時に破棄される
   
   
// func関数の戻り値は右辺値、zは左辺値
    return 0;
  // 戻り値はコピーも束縛もしていないため、以降使用できない
  }
func(z);
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>


== 左辺値参照と右辺値参照 ==
== メソッドの引数としての左辺値参照 ==
左辺値参照は、左辺値を束縛(※)すること、または、その参照変数のことである。<br>
==== 参照渡しによる効率化 ====
左辺値参照は右辺値を参照することができないが、<u>constが付加された左辺値参照は右辺値を参照する</u>ことができる。<br>
大きなオブジェクトを関数に渡す時、左辺値参照を使用することでコピーを避けられる。<br>
<br>
<br>
<u>※束縛</u><br>
<u>右辺値・左辺値に関わらず、参照を初期化することを束縛するという。</u><br>
<u>参照 = 束縛対象といった代入の形、または、束縛対象を関数の実引数にする形で束縛される。</u><br>
<u>参照を使用する時、束縛対象(参照元)の値を返す。</u><br>
<br>
<u>また、参照に参照を代入する時、代入元の束縛対象を代入先の束縛対象にする。</u><br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  class X {};
  #include <iostream>
#include <vector>
   
   
  void f(X &x) {}
// 値渡し : コピーが発生する
  void processByValue(std::vector<int> vec)
{
    std::cout << "Vector size: " << vec.size() << std::endl;
}
   
   
  void g(const X &x) {}
// 左辺値参照渡し : コピーが発生しない
  void processByReference(std::vector<int>& vec)
{
    std::cout << "Vector size: " << vec.size() << std::endl;
}
// const左辺値参照渡し : コピーが発生せず、変更もできない
void processByConstReference(const std::vector<int>& vec)
{
    std::cout << "Vector size: " << vec.size() << std::endl;
}
   
   
  int main()
  int main()
  {
  {
     X x;
     std::vector<int> largeVec(1000000, 42);
    f(x);    // コンパイル可能
            // コピーコンストラクタの引数は左辺値参照
    g(x);   // コンパイルエラー
            // Xクラスのインスタンスは左辺値のため、constが付加された引数には指定できない
   
   
     g(X()); // コンパイル可能
     processByValue(largeVec);          // コピーが発生 (遅い)
            // constが付加された引数は右辺値を参照できる
    processByReference(largeVec);     // コピーなし (速い)
    processByConstReference(largeVec); // コピーなし(速い)、変更不可
    return 0;
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
右辺値参照は、右辺値を束縛(※)すること、または、その参照変数のことである。<br>
==== 入出力パラメータ ====
右辺値参照は、<データ型>&& <変数名>と記述する。<br>
左辺値参照を使用して、メソッド内でオブジェクトを変更できる。<br>
<br>
<br>
右辺値参照は型であるため、左辺値になることができる。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  int func()
  #include <iostream>
#include <string>
// 出力パラメータ
void split(const std::string& fullName, std::string& firstName, std::string& lastName)
{
    size_t pos = fullName.find(' ');
    if (pos != std::string::npos)
    {
      firstName = fullName.substr(0, pos);
      lastName = fullName.substr(pos + 1);
    }
}
// 入出力パラメータ
void appendSuffix(std::string& str, const std::string& suffix)
  {
  {
     return 20;
     str += suffix;
  }
  }
   
   
  int main()
  int main()
  {
  {
     int&& i = 10;       // 右辺値10を右辺値参照変数iに束縛
     std::string fullName = "John Doe";
     int&& j = func();   // func関数の戻り値(右辺値)を右辺値参照変数jに束縛
     std::string first, last;
   
   
     std::cout << i << std::endl;  // 10
    split(fullName, first, last);
    std::cout << j << std::endl; // 20
     std::cout << "First: " << first << ", Last: " << last << std::endl;
   
   
     i = j;             // 右辺値参照は左辺値になることもできるため、コンパイル可能
     std::string message = "Hello";
    appendSuffix(message, " World!");
    std::cout << message << std::endl;  // Hello World!
   
   
     std::cout << i << std::endl; // 20
     return 0;
    std::cout << j << std::endl;  // 20
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
以下に、左辺値参照と右辺値参照の比較を示す。<br>
==== 関数の戻り値としての左辺値参照 ====
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  // []は束縛対象(参照元), <>はコピーされた値
  #include <iostream>
  int x      = 1;                  // xは左辺値<1>          1は右辺値
#include <vector>
int& lref1 = x;                 // lrefは左辺値参照[x]    xは左辺値
   
  int& lref2 = lref;               // lref2は左辺値参照[x]  lref1は左辺値参照[x]
class Container
  // int& lref3 = 1;               // コンパイルエラー
{
                                  // lref3は左辺値参照    1は右辺値
private:
int y      = lref1;             // yは左辺値<xの値>     lrefは左辺値参照[x]
    std::vector<int> data;
public:
    Container() : data{1, 2, 3, 4, 5} {}
    // 左辺値参照を返す
    int& at(size_t index)
    {
      return data.at(index);
    }
    // const左辺値参照を返す
    const int& at(size_t index) const
    {
      return data.at(index);
    }
};
  int main()
{
    Container c;
   
    // 左辺値参照を返すため、直接変更可能
    c.at(0) = 10;
    std::cout << c.at(0) << std::endl; // 10
    // 連鎖代入も可能
    c.at(1) = c.at(2) = 20;
    std::cout << c.at(1) << ", " << c.at(2) << std::endl; // 20, 20
   
   
int&& rref1 = 1;                // rref1が右辺値参照[1]  1は右辺値
    // const版の使用
// int&& rref2 = x;             // コンパイルエラー
    const Container constC;
                                  // rref2が右辺値参照     xは左辺値
    int value = constC.at(0);  // OK
// int&& rref3 = lref1;         // コンパイルエラー
    // constC.at(0) = 100;    // コンパイルエラー
                                  // rref3が右辺値参照     lrefは左辺値参照
  // int&& rref4 = rref1;          // コンパイエルエラー
                                  // rref4が右辺値参照     rrefは右辺値参照
int&& rrefm = std::move(lref1);  // rrefmが右辺値参照      std::move(lref1)は右辺値
int w      = rref1;             // wが左辺値<1>          rref1は右辺値参照[1]
</syntaxhighlight>
<br>
以下に、constを付加した左辺値参照も示す。<br>
一般的に、constを付加した右辺値参照は使用されない。(constを付加したオブジェクトの内部を変更すべきではないため)<br>
<syntaxhighlight lang="c++">
// []は束縛対象(参照元), <>はコピーされた値
int x            = 1
   
   
const int &clref1 = x;      // clref1がconst左辺値参照[1]  xは左辺値
    return 0;
const int &clref2 = lref1;  // clref2がconst左辺値参照[1] lref1は左辺値参照[1]
  }
const int &clref3 = 2;       // constが付加された左辺値参照は、右辺値を参照できる
                              // clref3がconst左辺値参照[2] 2は右辺値
  int z            = clref1;  // zが左辺値<1>  clrefはconst左辺値参照[1]
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>


== ムーブ ==
== 左辺値参照の実用例 ==
C++11以降、代入とコンストラクタにはコピーとムーブがある。<br>
==== 範囲for文での使用 ====
コピーは、データを全てコピーするため重い処理になるが、ムーブは、ポインタとサイズ情報のコピーのみを行うため軽い。<br>
ただし、ムーブ元のオブジェクトのデータは、ムーブ後は不定になるため、そのオブジェクトは使用できなくなる。<br>
<br>
ムーブ先のオブジェクトがムーブに対応している場合、左辺値を<code>std::move</code>メソッドの引数に指定して、<br>
代入、または、コンストラクタの引数に指定するだけでムーブができる。<br>
<br>
特に、右辺値参照はムーブで使用されることが多い。<br>
<br>
<u>※std::moveメソッド</u><br>
<u><code>std::move</code>メソッドは、左辺値を右辺値にキャストする。</u><br>
<u>右辺値なので、代入した場合は、<code>operartor=(const type&)</code>、<code>ムーブオペレータoperartor=(type&&)</code>が使用される。</u><br>
<u>ムーブオペレータは、ポインタの挿げ替えとムーブ元オブジェクトを無効にする機能が実装されている。</u><br>
<br>
例えば、aの値をbに代入する時、インスタンスのコピーが行われて、メモリ上に2つのインスタンスが存在することになる。<br>
もし、aが指すインスタンスが以降で使用されない場合、aのポインタをbが持つだけでよいため、コピー操作は必要無い。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  class Test;
  #include <iostream>
#include <vector>
int main()
{
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    // コピーが発生する
    for (auto num : numbers)
    {
      num *= 2;  // ローカルコピーを変更 (元のベクタは変わらない)
    }
    // 参照を使用 (元のベクタを変更)
    for (auto& num : numbers)
    {
      num *= 2;  // 元のベクタの要素を変更
    }
   
    // const参照を使用 (変更不可、コピーなし)
    for (const auto& num : numbers)
    {
      std::cout << num << " ";  // 読み取り専用
    }
    std::cout << std::endl;
   
   
Test a = Test();
    return 0;
Test b = a;
  }
  // aはこれ以降使用しない
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
ある変数が持つオブジェクトを別の変数に割り当てて、その変数からはオブジェクトを使わないようにする操作をムーブという。<br>
==== メンバ変数としての左辺値参照 ====
ムーブは、ムーブコンストラクタやムーブ演算子を使用して表現する。<br>
<br>
ムーブコンストラクタやムーブ演算子の引数に、右辺値参照が現れる。<br>
ムーブに対して、コピー演算子やコピーコンストラクタの引数は、左辺値参照となる。<br>
<br>
<code>std::move</code>メソッドは、左辺値を右辺値にキャストするものである。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  #include <iostream>
  #include <iostream>
  #include <utility>
  #include <string>
   
   
  class Counter
  class Logger
  {
  {
  private:
  private:
     int m_cnt;
     std::ostream& output; // 左辺値参照をメンバに持つ
   
   
  public:
  public:
     Counter() : m_cnt(0)
     // コンストラクタで参照を初期化(必須)
     {
     Logger(std::ostream& out) : output(out) {}
      std::cout << "Default" << std::endl;
    }
    Counter(const Counter &c)
    {
      std::cout << "Copy" << std::endl;
    }
   
   
     Counter(Counter &&c)
     void log(const std::string& message)
     {
     {
       m_cnt = c.getCnt();
       output << "[LOG] " << message << std::endl;
      std::cout << "Move" << std::endl;
     }
     }
    ~Counter() {}
    int getCnt() {return m_cnt;}
  };
  };
   
   
  int main()
  int main()
  {
  {
     Counter c1,                // 引数無しのコンストラクタ
     Logger consoleLogger(std::cout);
            c2;                // 引数無しのコンストラクタ
     consoleLogger.log("This goes to console");
    Counter c3(c1);            // コピーコンストラクタ
    Counter c4(std::move(c2)); // ムーブコンストラクタ
     std::string str1 = "abc";
   
   
     std::string str2 = str1;             // str2をコピー
     std::ofstream file("log.txt");
     std::string str3 = std::move(str1); // str3にstr1の内容をムーブする。以降str1の内容は不定となる
     Logger fileLogger(file);
     std::string str4(std::move(str3));   // str4にstr3の内容をムーブする。以降str3の内容は不定となる
     fileLogger.log("This goes to file");
   
   
     return 0;
     return 0;
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br>
 
==== スワップ操作 ====
== ユニバーサル参照(&&で左辺値も束縛できる特別な例外) ==
<code>auto</code>変数や<code>template</code>変数の<code>&&</code>による参照は、右辺値だけでなく、左辺値も束縛できる。<br>
ユニバーサル参照は、次のセクションに記載する完全転送に関わる。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  // []は束縛対象(参照元), <>はコピーされた値
#include <iostream>
#include <utility>
  // 左辺値参照を使用したスワップ
void swap(int& a, int& b)
{
    int temp = a;
    a = b;
    b = temp;
}
   
   
  // g関数に渡した引数が左辺値の場合は左辺値参照となり、右辺値の場合は右辺値参照となる。
  // より効率的なムーブを使用したスワップ
  template <typename T>
  template<typename T>
  void g(T&& uref) {}
  void swapMove(T& a, T& b)
{
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}
   
   
  int x            = 1;      // xは左辺値                      1は右辺値
  int main()
  int& lref1        = x;      // lref1が左辺値参照              xは左辺値
  {
const int& clref1 = x;       // clref1はconst左辺値参照        xは左辺値
    int x = 10, y = 20;
   
   
int&& rref1      = 1;      // rref1が右辺値参照[1]          1は右辺値
    std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
// int&& rref2    = x;      // コンパイルエラー
    swap(x, y);
                              // rref2が右辺値参照             lref1が左辺値参照
    std::cout << "After swap: x = " << x << ", y = " << y << std::endl;
// int&& rref3    = lref1;   // コンパイルエラー
                              // rref3が右辺値参照             lref1が左辺値参照
// int&& rref4    = rref1;  // コンパイルエラー
                              // rref4が右辺値参照             rref1が右辺値参照(左辺値)
// int&& rref5    = clref1; // コンパイルエラー
                              // rref5が右辺値参照             clref1がconst左辺値参照
   
   
auto&& uref1      = 1;      // uref1がユニバーサル参照[1]     1は右辺値 
     std::string s1 = "Hello", s2 = "World";
auto&& uref2      = x;      // uref2がユニバーサル参照[x]    xは左辺値 
auto&& uref3      = lref1;   // uref3がユニバーサル参照[x]    lref1が左辺値参照[x]
auto&& uref4      = rref1;  // uref4がユニバーサル参照[1]    rref1が右辺値参照[1]
auto&& uref5      = clref1;  // uref5がユニバーサル参照[x]    clref1がconst左辺値参照[x]
   
   
g(1);                       // g()の引数はユニバーサル参照  1は右辺値
    std::cout << "Before swap: s1 = " << s1 << ", s2 = " << s2 << std::endl;
g(x);                       // g()の引数はユニバーサル参照  xは左辺値
    swapMove(s1, s2);
g(lref1);                   // g()の引数はユニバーサル参照  lref1は左辺値参照
    std::cout << "After swap: s1 = " << s1 << ", s2 = " << s2 << std::endl;
  g(rref1);                   // g()の引数はユニバーサル参照  rref1が右辺値参照
   
  g(clref1);                  // g()の引数はユニバーサル参照  clref1がconst左辺値参照
    return 0;
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>


== 完全転送 ==
== その他 ==
完全転送とは、ユニバーサル参照が束縛した値において、右辺値・左辺値の型情報も保持して転送すること。<br>
==== ダングリング参照 ====
完全転送を行うには、<code>std::forward</code>メソッドを使用する。<br>
参照が指すオブジェクトのスコープが終了すると、ダングリング参照(dangling reference)が発生する。<br>
<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  // 左辺値参照関数
  #include <iostream>
  void ref(int& a)
  int& getDanglingReference()
  {
  {
     std::cout<< "左辺値参照" <<std::endl;
     int local = 42;
    return local;  // 危険: ローカル変数への参照を返す
  }
  }
   
   
  // 右辺値参照関数
  int main()
// 引数bは右辺値のみ受け取る
void ref(int&& b)
  {
  {
     std::cout << "右辺値参照" << std::endl;
     // int& ref = getDanglingReference();  // 未定義動作
    // std::cout << ref << std::endl;      // 危険
    return 0;
  }
  }
</syntaxhighlight>
<br>
==== 参照の初期化は必須 ====
<syntaxhighlight lang="c++">
int main()
{
    // int& ref;  // コンパイルエラー: 参照は初期化が必須
   
   
  // 左辺値参照関数が呼び出される
    int x = 10;
  template <typename T>
    int& ref = x; // OK
  void h(T&& uref)
    return 0;
}
</syntaxhighlight>
<br>
==== 参照の配列は作成できない ====
  <syntaxhighlight lang="c++">
  int main()
  {
  {
     ref(uref);
     int x = 10, y = 20, z = 30;
    // int& refs[3] = {x, y, z};  // コンパイルエラー: 参照の配列は不可
    // ポインタの配列は可能
    int* ptrs[3] = {&x, &y, &z};  // OK
    return 0;
  }
  }
   
  </syntaxhighlight>
// 右辺値参照版・左辺値参照版のどちらが呼ばれるかは引数により変わる
<br>
  template <typename T>
==== nullptrや未初期化の参照は存在しない ====
  void h_with_forward(T&& uref)
  <syntaxhighlight lang="c++">
  int main()
  {
  {
     ref(std::forward<T>(uref));
     // 参照はnullにできない
  }
    // int& ref = nullptr// コンパイルエラー
   
   
std::string str = "abc";
    // ポインタとの違い
int x          = 1;
    int* ptr = nullptr; // OK: ポインタはnullにできる
   
   
h(std::move(str));              // std::move(str)は右辺値  出力:左辺値参照
    return 0;
h(1);                            // 1は右辺値  出力:左辺値参照
  }
h(x);                           // xは左辺値  出力:左辺値参照
   
h_with_forward(std::move(str));  // std::move(str)は右辺値  出力:右辺値参照
h_with_forward(1);              // 1は右辺値  出力:右辺値参照
h_with_forward(x);              // xは左辺値  出力:左辺値参照
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>


== 対応表 ==
== 左辺値参照とポインタの比較 ==
下表に、変数Aに変数Bを代入する時(A = B)の対応表を示す。<br>
左辺値参照とポインタは似ているが、重要な違いがある。<br>
<br>
<center>
<center>
{| class="wikitable" style="background-color:#fefefe;"
{| class="wikitable" style="background-color:#fefefe;"
|-
|-
! style="background-color:#00ffff;" | A\B
! 特性 !! 左辺値参照 !! ポインタ
! style="background-color:#00ffff;" | 左辺値
! style="background-color:#00ffff;" | const左辺値
! style="background-color:#00ffff;" | 左辺値参照  
! style="background-color:#00ffff;" | const左辺値参照
! style="background-color:#00ffff;" | 右辺値
! style="background-color:#00ffff;" | 右辺値参照
! style="background-color:#00ffff;" | ユニバーサル参照
|-
|-
| 左辺値 || コピー || コピー || コピー || コピー || コピー || コピー || コピー
| 初期化 || 必須 || 任意
|-
|-
| const左辺値 || コピー || コピー || コピー || コピー || コピー || コピー || コピー
| null値 || 不可 || 可能
|-
|-
| 左辺値参照 || 束縛 || × || 束縛 || × || × || 束縛 || 束縛
| 再代入 || 不可 (束縛先の値を変更) || 可能 (別のアドレスを指す)
|-
|-
| const左辺値参照 || 束縛 || 束縛 || 束縛 || 束縛 || 束縛 || 束縛 || 束縛
| 構文 || シンプル (オブジェクトと同じ) || *演算子が必要
|-
|-
| 右辺値参照 || × || × || × || × || 束縛 || × || ×
| オーバーヘッド || 通常なし (最適化される) || あり (間接参照)
|-
|-
| ユニバーサル参照 || 束縛 || 束縛 || 束縛 || 束縛 || 束縛 || 束縛 || 束縛
| 配列 || 作成不可 || 作成可能
|-
| ポインタ演算 || 不可 || 可能
|}
|}
</center>
</center>
<br>
<syntaxhighlight lang="c++">
#include <iostream>
int main()
{
    int x = 10;
    int y = 20;
    // 左辺値参照
    int& ref = x;
    ref = y;  // xの値が20になる (refがyを指すわけではない)
    std::cout << "x: " << x << std::endl;  // 20
    // ポインタ
    int* ptr = &x;
    ptr = &y;  // ptrがyを指すようになる
    std::cout << "x: " << x << std::endl;  // 20(変わらない)
    return 0;
}
</syntaxhighlight>
<br><br>
<br><br>
== 最適化とインライン展開 ==
コンパイラは、左辺値参照を最適化することが多い。<br>
特に、小さな関数では、参照がインライン展開され、実質的なオーバーヘッドが無くなる。<br>
<br>
<syntaxhighlight lang="c++">
inline int& getMax(int& a, int& b)
{
    return (a > b) ? a : b;
}
int main()
{
    int x = 10, y = 20;
    // インライン展開により、効率的なコードが生成される
    getMax(x, y) = 30;  // yが30になる
    return 0;
}
</syntaxhighlight>
<br><br>
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:C++]]
[[カテゴリ:C++]]