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

提供: MochiuWiki : SUSE, EC, PCB

ページの作成:「== 概要 == C言語では、代入演算子=の左側にあるものを左辺値(lvalue)、右側(rvalue)にあるものを右辺値として決められている。<br>…」
 
編集の要約なし
 
(同じ利用者による、間の8版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
C言語では、代入演算子=の左側にあるものを左辺値(lvalue)、右側(rvalue)にあるものを右辺値として決められている。<br>
左辺値 (lvalue) は、C言語から存在する基本的な概念であり、メモリ上の特定の場所を持つオブジェクトを指す。<br>
C++では、左辺値は名前を持つオブジェクト(&演算子でアドレスを取得できる)、右辺値は名前を持たない一時オブジェクトのことである。<br>
C++では、左辺値参照 (lvalue reference) を使用して、オブジェクトへの別名を作成できる。<br>
<br><br>
 
== 左辺値 (lvalue) ==
==== 定義 ====
左辺値とは、メモリ上の特定のアドレスを持つオブジェクトを指す。<br>
名前を持ち、プログラム実行中に識別可能なエンティティである。<br>
<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;
    // 配列名は左辺値
    int* arrPtr = arr;
    // 文字列リテラルは左辺値(特殊なケース)
    const char* str = "Hello";  // "Hello"は左辺値
    // 関数は左辺値
    int (*funcPtr)() = globalVar;  // 関数へのポインタ
    // すべての左辺値はアドレスを取得できる
    int* xAddr = &x;
    int* yAddr = &y;
    int* arrElemAddr = &arr[0];
    int* objValueAddr = &(obj.value);
    return 0;
}
</syntaxhighlight>
<br>
==== 左辺値になるもの ====
以下に示すものは左辺値である。<br>
* 変数名
* 配列要素
* クラスのメンバ変数
* ポインタのデリファレンス
* 文字列リテラル
*: const char[]型
* 左辺値参照を返す関数呼び出し
* 前置インクリメント・デクリメント
*: ++x, --x
* 代入式
*: 例 : int x = y
<br>
==== 左辺値にならないもの ====
以下に示すものは左辺値ではなく、右辺値である。<br>
* 数値リテラル
*: 例 : 42, 3.14等
* 算術演算の結果
*: x + y
* 非参照を返す関数呼び出し
* 後置インクリメント・デクリメント
*: x++, x--
* 一時オブジェクト
<br><br>
<br><br>


== 右辺値 ==
== 左辺値参照 (lvalue reference) ==
メソッドの戻り値、式の結果等は右辺値である。<br>
==== 定義 ====
左辺値参照は、既存のオブジェクトへの別名 (エイリアス) である。<br>
C++98から存在する従来の参照型であり、<u>型名&</u> という記法で表現される。<br>
<br>
==== 基本的な構文 ====
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  class X;
  int x = 10;
int& ref = x;        // 左辺値参照の宣言と初期化
   
   
  int func();
  const int& cref = x;  // const左辺値参照
</syntaxhighlight>
<br>
==== 左辺値参照の特性 ====
左辺値参照は、以下に示す特性を持つ。<br>
* 必ず初期化時に束縛する必要がある。
* 1度束縛すると、別のオブジェクトに再束縛できない。
* 参照自体はメモリを持たない。(コンパイラによる最適化の対象)
* 参照を通じた操作は、束縛したオブジェクトへの操作と同じである。
* 左辺値参照は、左辺値のみを束縛できる。
* const左辺値参照は、左辺値と右辺値の両方を束縛できる。
<br>
==== 使用例 ====
<syntaxhighlight lang="c++">
#include <iostream>
   
   
  int main()
  int main()
  {
  {
     X();       // クラスのコンストラクタは右辺値
     int x = 10;
     func();    // func関数の戻り値は右辺値
    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
    // 変数を通じた値の変更
    x = 30;
    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;
   
   
     int i = 1; // 変数iは左辺値, 1は右辺値
     return 0;
    i + 1;      // 式i + 1は右辺値
  }
  }
  </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;
    // 左辺値参照は左辺値を束縛できる
     int& ref1 = x;  // OK
    // 左辺値参照は右辺値を束縛できない
    // int& ref2 = 10;    // コンパイルエラー
    // int& ref3 = x + y;  // コンパイルエラー
    // 参照の「代入」は、束縛先の値を変更する
    int& ref2 = y;
    ref1 = ref2;  // xの値がyの値になる(ref1がref2を指すわけではない)
    std::cout << "x: " << x << std::endl;  // 20
    std::cout << "y: " << y << std::endl;  // 20
    // 参照を再束縛することはできない
    // ref1はxへの別名であり続ける
    return 0;
  }
  }
<br><br>
</syntaxhighlight>
 
<br>
== 左辺値 ==
==== const左辺値参照 ====
左辺値の例<br>
const左辺値参照は、特別な性質を持つ。<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クラスのインスタンスは左辺値
   
   
     X *ptr_X = &x;  // Xクラスのポインタは左辺値
     // const左辺値参照は左辺値を束縛できる
    const int& cref1 = x;
    // const左辺値参照は右辺値を束縛できる
    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>
左辺値参照は右辺値を参照することができないが、constが付加された左辺値参照は右辺値を参照することができる。
<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  class X {};
  #include <iostream>
#include <string>
   
   
  void f(X &x) {}
  class TempObject
{
public:
    TempObject() { std::cout << "TempObject created" << std::endl; }
    ~TempObject() { std::cout << "TempObject destroyed" << std::endl; }
   
   
void g(const X &x) {}
    void use() const { std::cout << "Using TempObject" << std::endl; }
};
TempObject createObject()
{
    return TempObject();
}
   
   
  int main()
  int main()
  {
  {
     X x;
     std::cout << "--- Without reference ---" << std::endl;
     f(x);   // コンパイル可能
     createObject().use(); // 一時オブジェクトはすぐに破棄される
            // コピーコンストラクタの引数は左辺値参照
     g(x);   // コンパイルエラー
    std::cout << "\n--- With const lvalue reference ---" << std::endl;
            // Xクラスのインスタンスは左辺値のため、constが付加された引数には指定できない
    const TempObject& ref = createObject();  // 寿命が延長される
     ref.use();
    std::cout << "Still in scope" << std::endl;
    // スコープ終了時に破棄される
   
   
     g(X()); // コンパイル可能
     return 0;
            // constが付加された引数は右辺値を参照できる
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>


== 右辺値参照 ==
== メソッドの引数としての左辺値参照 ==
右辺値参照とは、右辺値への参照である。
==== 参照渡しによる効率化 ====
右辺値参照は、<データ型>&& <変数名>と記述する。
大きなオブジェクトを関数に渡す時、左辺値参照を使用することでコピーを避けられる。<br>
 
<br>
右辺値参照は型であるため、左辺値になることができる。
また、右辺値・左辺値に関わらず、参照を初期化することを束縛するという。
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  int func()
  #include <iostream>
#include <vector>
// 値渡し : コピーが発生する
void processByValue(std::vector<int> vec)
  {
  {
     return 20;
     std::cout << "Vector size: " << vec.size() << std::endl;
}
// 左辺値参照渡し : コピーが発生しない
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()
  {
  {
     int&& i = 10;      // 右辺値10を右辺値参照変数iに束縛
     std::vector<int> largeVec(1000000, 42);
    int&& j = func();   // func関数の戻り値(右辺値)を右辺値参照変数jに束縛
   
   
     cout << i << endl; // 10
     processByValue(largeVec);         // コピーが発生 (遅い)
     cout << j << endl; // 20
     processByReference(largeVec);     // コピーなし (速い)
    processByConstReference(largeVec); // コピーなし(速い)、変更不可
   
   
     i = j;             // 右辺値参照は左辺値になることもできるため、コンパイル可能
     return 0;
    cout << i << endl;  // 20
    cout << j << endl;  // 20
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
右辺値参照は、ムーブで使用される.<br>
==== 入出力パラメータ ====
ムーブとは、あるオブジェクト(左辺値や一時オブジェクト等)をコピーすることなく他の変数に割り当てる操作である。<br>
左辺値参照を使用して、メソッド内でオブジェクトを変更できる。<br>
<br>
<br>
例えば、aの値をbに代入する時、インスタンスのコピーが行われて、メモリ上に2つのインスタンスが存在することになる。<br>
もし、aが指すインスタンスが以降で使用されない場合、aのポインタをbが持つだけでよいため、コピー操作は必要無い。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  class Test;
  #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)
{
    str += suffix;
}
   
   
  Test a = Test();
  int main()
  Test b = a;
{
  // aはこれ以降使用しない
    std::string fullName = "John Doe";
    std::string first, last;
    split(fullName, first, last);
    std::cout << "First: " << first << ", Last: " << last << std::endl;
   
    std::string message = "Hello";
    appendSuffix(message, " World!");
    std::cout << message << std::endl; // Hello World!
    return 0;
}
  </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 <vector>
   
   
  class Counter
  class Container
  {
  {
  private:
  private:
     int m_cnt;
     std::vector<int> data;
   
   
  public:
  public:
     Counter() : m_cnt(0)
     Container() : data{1, 2, 3, 4, 5} {}
    // 左辺値参照を返す
    int& at(size_t index)
     {
     {
       std::cout << "Default" << std::endl;
       return data.at(index);
     }
     }
   
   
     Counter(const Counter &c)
     // const左辺値参照を返す
    const int& at(size_t index) const
     {
     {
       std::cout << "Copy" << std::endl;
       return data.at(index);
     }
     }
};
int main()
{
    Container c;
   
   
     Counter(Counter &&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
    // const版の使用
    const Container constC;
    int value = constC.at(0);  // OK
    // constC.at(0) = 100;    // コンパイルエラー
    return 0;
}
</syntaxhighlight>
<br><br>
 
== 左辺値参照の実用例 ==
==== 範囲for文での使用 ====
<syntaxhighlight lang="c++">
#include <iostream>
#include <vector>
int main()
{
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    // コピーが発生する
    for (auto num : numbers)
     {
     {
       m_cnt = c.getCnt();
       num *= 2;  // ローカルコピーを変更 (元のベクタは変わらない)
      std::cout << "Move" << std::endl;
     }
     }
   
   
     ~Counter() {}
     // 参照を使用 (元のベクタを変更)
    for (auto& num : numbers)
    {
      num *= 2;  // 元のベクタの要素を変更
    }
   
    // const参照を使用 (変更不可、コピーなし)
    for (const auto& num : numbers)
    {
      std::cout << num << " ";  // 読み取り専用
    }
    std::cout << std::endl;
    return 0;
}
</syntaxhighlight>
<br>
==== メンバ変数としての左辺値参照 ====
<syntaxhighlight lang="c++">
#include <iostream>
#include <string>
class Logger
{
private:
    std::ostream& output;  // 左辺値参照をメンバに持つ
public:
    // コンストラクタで参照を初期化(必須)
    Logger(std::ostream& out) : output(out) {}
   
   
     int getCnt() {return m_cnt;}
     void log(const std::string& message)
    {
      output << "[LOG] " << message << std::endl;
    }
  };
  };
   
   
  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::ofstream file("log.txt");
    Logger fileLogger(file);
    fileLogger.log("This goes to file");
    return 0;
}
</syntaxhighlight>
<br>
==== スワップ操作 ====
<syntaxhighlight lang="c++">
#include <iostream>
#include <utility>
// 左辺値参照を使用したスワップ
void swap(int& a, int& b)
{
    int temp = a;
    a = b;
    b = temp;
}
// より効率的なムーブを使用したスワップ
template<typename T>
void swapMove(T& a, T& b)
{
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}
int main()
{
    int x = 10, y = 20;
    std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
    swap(x, y);
    std::cout << "After swap: x = " << x << ", y = " << y << std::endl;
    std::string s1 = "Hello", s2 = "World";
    std::cout << "Before swap: s1 = " << s1 << ", s2 = " << s2 << std::endl;
    swapMove(s1, s2);
    std::cout << "After swap: s1 = " << s1 << ", s2 = " << s2 << std::endl;
    return 0;
}
</syntaxhighlight>
<br><br>
 
== その他 ==
==== ダングリング参照 ====
参照が指すオブジェクトのスコープが終了すると、ダングリング参照(dangling reference)が発生する。<br>
<br>
<syntaxhighlight lang="c++">
#include <iostream>
int& getDanglingReference()
{
    int local = 42;
    return local;  // 危険: ローカル変数への参照を返す
}
int main()
{
     // int& ref = getDanglingReference(); // 未定義動作
     // std::cout << ref << std::endl;      // 危険
    return 0;
}
</syntaxhighlight>
<br>
==== 参照の初期化は必須 ====
<syntaxhighlight lang="c++">
int main()
{
    // int& ref;  // コンパイルエラー: 参照は初期化が必須
    int x = 10;
    int& ref = x;  // OK
    return 0;
}
</syntaxhighlight>
<br>
==== 参照の配列は作成できない ====
<syntaxhighlight lang="c++">
int main()
{
    int x = 10, y = 20, z = 30;
    // int& refs[3] = {x, y, z};  // コンパイルエラー: 参照の配列は不可
    // ポインタの配列は可能
    int* ptrs[3] = {&x, &y, &z};  // OK
    return 0;
}
</syntaxhighlight>
<br>
==== nullptrや未初期化の参照は存在しない ====
<syntaxhighlight lang="c++">
int main()
{
    // 参照はnullにできない
    // int& ref = nullptr;  // コンパイルエラー
    // ポインタとの違い
    int* ptr = nullptr;  // OK: ポインタはnullにできる
    return 0;
}
</syntaxhighlight>
<br><br>
 
== 左辺値参照とポインタの比較 ==
左辺値参照とポインタは似ているが、重要な違いがある。<br>
<br>
<center>
{| class="wikitable" style="background-color:#fefefe;"
|-
! 特性 !! 左辺値参照 !! ポインタ
|-
| 初期化 || 必須 || 任意
|-
| null値 || 不可 || 可能
|-
| 再代入 || 不可 (束縛先の値を変更) || 可能 (別のアドレスを指す)
|-
| 構文 || シンプル (オブジェクトと同じ) || *演算子が必要
|-
| オーバーヘッド || 通常なし (最適化される) || あり (間接参照)
|-
| 配列 || 作成不可 || 作成可能
|-
| ポインタ演算 || 不可 || 可能
|}
</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>
<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;
     return 0;
159行目: 624行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<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++]]

2026年1月5日 (月) 21:43時点における最新版

概要

左辺値 (lvalue) は、C言語から存在する基本的な概念であり、メモリ上の特定の場所を持つオブジェクトを指す。
C++では、左辺値参照 (lvalue reference) を使用して、オブジェクトへの別名を作成できる。


左辺値 (lvalue)

定義

左辺値とは、メモリ上の特定のアドレスを持つオブジェクトを指す。
名前を持ち、プログラム実行中に識別可能なエンティティである。

特徴

左辺値は、以下に示す特徴を持つ。

  • 名前を持つ変数や、参照、デリファレンス演算子(*)を使用した式等が該当する。
  • 代入演算子の左側に現れることができる。
  • &演算子を使用して、そのアドレスを取得できる。
  • プログラム実行中、一定期間メモリ上に存在し続ける。
  • 識別可能であり、複数回参照できる。


左辺値の例

 #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;
 
    // 配列名は左辺値
    int* arrPtr = arr;
 
    // 文字列リテラルは左辺値(特殊なケース)
    const char* str = "Hello";  // "Hello"は左辺値
 
    // 関数は左辺値
    int (*funcPtr)() = globalVar;  // 関数へのポインタ
 
    // すべての左辺値はアドレスを取得できる
    int* xAddr = &x;
    int* yAddr = &y;
    int* arrElemAddr = &arr[0];
    int* objValueAddr = &(obj.value);
 
    return 0;
 }


左辺値になるもの

以下に示すものは左辺値である。

  • 変数名
  • 配列要素
  • クラスのメンバ変数
  • ポインタのデリファレンス
  • 文字列リテラル
    const char[]型
  • 左辺値参照を返す関数呼び出し
  • 前置インクリメント・デクリメント
    ++x, --x
  • 代入式
    例 : int x = y


左辺値にならないもの

以下に示すものは左辺値ではなく、右辺値である。

  • 数値リテラル
    例 : 42, 3.14等
  • 算術演算の結果
    x + y
  • 非参照を返す関数呼び出し
  • 後置インクリメント・デクリメント
    x++, x--
  • 一時オブジェクト



左辺値参照 (lvalue reference)

定義

左辺値参照は、既存のオブジェクトへの別名 (エイリアス) である。
C++98から存在する従来の参照型であり、型名& という記法で表現される。

基本的な構文

 int x = 10;
 int& ref = x;         // 左辺値参照の宣言と初期化
 
 const int& cref = x;  // const左辺値参照


左辺値参照の特性

左辺値参照は、以下に示す特性を持つ。

  • 必ず初期化時に束縛する必要がある。
  • 1度束縛すると、別のオブジェクトに再束縛できない。
  • 参照自体はメモリを持たない。(コンパイラによる最適化の対象)
  • 参照を通じた操作は、束縛したオブジェクトへの操作と同じである。
  • 左辺値参照は、左辺値のみを束縛できる。
  • const左辺値参照は、左辺値と右辺値の両方を束縛できる。


使用例

 #include <iostream>
 
 int main()
 {
    int x = 10;
    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
 
    // 変数を通じた値の変更
    x = 30;
    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;
 }


左辺値参照の束縛

 #include <iostream>
 
 int main()
 {
    int x = 10;
    int y = 20;
 
    // 左辺値参照は左辺値を束縛できる
    int& ref1 = x;  // OK
 
    // 左辺値参照は右辺値を束縛できない
    // int& ref2 = 10;     // コンパイルエラー
    // int& ref3 = x + y;  // コンパイルエラー
 
    // 参照の「代入」は、束縛先の値を変更する
    int& ref2 = y;
    ref1 = ref2;  // xの値がyの値になる(ref1がref2を指すわけではない)
 
    std::cout << "x: " << x << std::endl;  // 20
    std::cout << "y: " << y << std::endl;  // 20
 
    // 参照を再束縛することはできない
    // ref1はxへの別名であり続ける
 
    return 0;
 }


const左辺値参照

const左辺値参照は、特別な性質を持つ。

 #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 x = 10;
 
    // const左辺値参照は左辺値を束縛できる
    const int& cref1 = x;
 
    // const左辺値参照は右辺値を束縛できる
    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;
 }


一時オブジェクトの寿命延長

const左辺値参照に束縛された一時オブジェクトは、参照のスコープが終了するまで寿命が延長される。

 #include <iostream>
 #include <string>
 
 class TempObject
 {
 public:
    TempObject() { std::cout << "TempObject created" << std::endl; }
    ~TempObject() { std::cout << "TempObject destroyed" << std::endl; }
 
    void use() const { std::cout << "Using TempObject" << std::endl; }
 };
 
 TempObject createObject()
 {
    return TempObject();
 }
 
 int main()
 {
    std::cout << "--- Without reference ---" << std::endl;
    createObject().use();  // 一時オブジェクトはすぐに破棄される
 
    std::cout << "\n--- With const lvalue reference ---" << std::endl;
    const TempObject& ref = createObject();  // 寿命が延長される
    ref.use();
    std::cout << "Still in scope" << std::endl;
    // スコープ終了時に破棄される
 
    return 0;
 }



メソッドの引数としての左辺値参照

参照渡しによる効率化

大きなオブジェクトを関数に渡す時、左辺値参照を使用することでコピーを避けられる。

 #include <iostream>
 #include <vector>
 
 // 値渡し : コピーが発生する
 void processByValue(std::vector<int> vec)
 {
    std::cout << "Vector size: " << vec.size() << std::endl;
 }
 
 // 左辺値参照渡し : コピーが発生しない
 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()
 {
    std::vector<int> largeVec(1000000, 42);
 
    processByValue(largeVec);          // コピーが発生 (遅い)
    processByReference(largeVec);      // コピーなし (速い)
    processByConstReference(largeVec); // コピーなし(速い)、変更不可
 
    return 0;
 }


入出力パラメータ

左辺値参照を使用して、メソッド内でオブジェクトを変更できる。

 #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)
 {
    str += suffix;
 }
 
 int main()
 {
    std::string fullName = "John Doe";
    std::string first, last;
 
    split(fullName, first, last);
    std::cout << "First: " << first << ", Last: " << last << std::endl;
 
    std::string message = "Hello";
    appendSuffix(message, " World!");
    std::cout << message << std::endl;  // Hello World!
 
    return 0;
 }


関数の戻り値としての左辺値参照

 #include <iostream>
 #include <vector>
 
 class Container
 {
 private:
    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
 
    // const版の使用
    const Container constC;
    int value = constC.at(0);  // OK
    // constC.at(0) = 100;     // コンパイルエラー
 
    return 0;
 }



左辺値参照の実用例

範囲for文での使用

 #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;
 
    return 0;
 }


メンバ変数としての左辺値参照

 #include <iostream>
 #include <string>
 
 class Logger
 {
 private:
    std::ostream& output;  // 左辺値参照をメンバに持つ
 
 public:
    // コンストラクタで参照を初期化(必須)
    Logger(std::ostream& out) : output(out) {}
 
    void log(const std::string& message)
    {
       output << "[LOG] " << message << std::endl;
    }
 };
 
 int main()
 {
    Logger consoleLogger(std::cout);
    consoleLogger.log("This goes to console");
 
    std::ofstream file("log.txt");
    Logger fileLogger(file);
    fileLogger.log("This goes to file");
 
    return 0;
 }


スワップ操作

 #include <iostream>
 #include <utility>
 
 // 左辺値参照を使用したスワップ
 void swap(int& a, int& b)
 {
    int temp = a;
    a = b;
    b = temp;
 }
 
 // より効率的なムーブを使用したスワップ
 template<typename T>
 void swapMove(T& a, T& b)
 {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
 }
 
 int main()
 {
    int x = 10, y = 20;
 
    std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
    swap(x, y);
    std::cout << "After swap: x = " << x << ", y = " << y << std::endl;
 
    std::string s1 = "Hello", s2 = "World";
 
    std::cout << "Before swap: s1 = " << s1 << ", s2 = " << s2 << std::endl;
    swapMove(s1, s2);
    std::cout << "After swap: s1 = " << s1 << ", s2 = " << s2 << std::endl;
 
    return 0;
 }



その他

ダングリング参照

参照が指すオブジェクトのスコープが終了すると、ダングリング参照(dangling reference)が発生する。

 #include <iostream>
 
 int& getDanglingReference()
 {
    int local = 42;
    return local;  // 危険: ローカル変数への参照を返す
 }
 
 int main()
 {
    // int& ref = getDanglingReference();  // 未定義動作
    // std::cout << ref << std::endl;      // 危険
 
    return 0;
 }


参照の初期化は必須

 int main()
 {
    // int& ref;  // コンパイルエラー: 参照は初期化が必須
 
    int x = 10;
    int& ref = x;  // OK
 
    return 0;
 }


参照の配列は作成できない

 int main()
 {
    int x = 10, y = 20, z = 30;
 
    // int& refs[3] = {x, y, z};  // コンパイルエラー: 参照の配列は不可
 
    // ポインタの配列は可能
    int* ptrs[3] = {&x, &y, &z};  // OK
 
    return 0;
 }


nullptrや未初期化の参照は存在しない

 int main()
 {
    // 参照はnullにできない
    // int& ref = nullptr;  // コンパイルエラー
 
    // ポインタとの違い
    int* ptr = nullptr;  // OK: ポインタはnullにできる
 
    return 0;
 }



左辺値参照とポインタの比較

左辺値参照とポインタは似ているが、重要な違いがある。

特性 左辺値参照 ポインタ
初期化 必須 任意
null値 不可 可能
再代入 不可 (束縛先の値を変更) 可能 (別のアドレスを指す)
構文 シンプル (オブジェクトと同じ) *演算子が必要
オーバーヘッド 通常なし (最適化される) あり (間接参照)
配列 作成不可 作成可能
ポインタ演算 不可 可能


 #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;
 }



最適化とインライン展開

コンパイラは、左辺値参照を最適化することが多い。
特に、小さな関数では、参照がインライン展開され、実質的なオーバーヘッドが無くなる。

 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;
 }