「C++の基礎 - 右辺値と左辺値」の版間の差分
(→左辺値) |
編集の要約なし |
||
6行目: | 6行目: | ||
<br><br> | <br><br> | ||
== | == 右辺値と左辺値 == | ||
メソッドの戻り値、式の結果等は右辺値である。<br> | メソッドの戻り値、式の結果等は右辺値である。<br> | ||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
31行目: | 31行目: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
左辺値とは、変数に代入されている値のことである。<br> | 左辺値とは、変数に代入されている値のことである。<br> | ||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
45行目: | 43行目: | ||
X *ptr_X = &x; // ポインタ変数は左辺値 | X *ptr_X = &x; // ポインタ変数は左辺値 | ||
} | } | ||
</syntaxhighlight> | |||
<br> | |||
以下に、右辺値と左辺値の比較を示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
int x = 1; // xは左辺値、1は右辺値 | |||
int y = x + 1; // yは左辺値、式x + 1は右辺値、xは左辺値 | |||
int z = func(y + 2); // zは左辺値、func関数の戻り値は右辺値、式y + 2も右辺値 | |||
struct Point | |||
{ | |||
int x = 0; | |||
int y = 0; | |||
}; | |||
Point pt = Point(); // Pointクラスのインスタンスptは左辺値、Pointクラスのコンストラクタは右辺値 | |||
1; // 1は右辺値 | |||
func(z); // func関数の戻り値は右辺値 | |||
// 戻り値はコピーも束縛もしていないため、以降使用できない | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
== | == 左辺値参照と右辺値参照 == | ||
左辺値参照は、左辺値を束縛(※)すること、または、その参照変数のことである。<br> | |||
左辺値参照は右辺値を参照することができないが、<u>constが付加された左辺値参照は右辺値を参照する</u>ことができる。<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 {}; | class X {}; | ||
69行目: | 94行目: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br> | ||
右辺値参照は、右辺値を束縛(※)すること、または、その参照変数のことである。<br> | |||
右辺値参照は、<データ型>&& <変数名>と記述する。<br> | |||
<br> | |||
右辺値参照は、<データ型>&& <変数名>と記述する。 | 右辺値参照は型であるため、左辺値になることができる。<br> | ||
右辺値参照は型であるため、左辺値になることができる。 | |||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
int func() | int func() | ||
88行目: | 110行目: | ||
int&& j = func(); // func関数の戻り値(右辺値)を右辺値参照変数jに束縛 | int&& j = func(); // func関数の戻り値(右辺値)を右辺値参照変数jに束縛 | ||
cout << i << endl; // 10 | std::cout << i << std::endl; // 10 | ||
cout << j << endl; // 20 | std::cout << j << std::endl; // 20 | ||
i = j; // 右辺値参照は左辺値になることもできるため、コンパイル可能 | i = j; // 右辺値参照は左辺値になることもできるため、コンパイル可能 | ||
cout << i << endl; // 20 | std::cout << i << std::endl; // 20 | ||
cout << j << endl; // 20 | std::cout << j << std::endl; // 20 | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
以下に、左辺値参照と右辺値参照の比較を示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
// []は束縛対象(参照元), <>はコピーされた値 | |||
int x = 1; // xは左辺値<1> 1は右辺値 | |||
int& lref1 = x; // lrefは左辺値参照[x] xは左辺値 | |||
int& lref2 = lref; // lref2は左辺値参照[x] lref1は左辺値参照[x] | |||
// int& lref3 = 1; // コンパイルエラー | |||
// lref3は左辺値参照 1は右辺値 | |||
int y = lref1; // yは左辺値<xの値> lrefは左辺値参照[x] | |||
int&& rref1 = 1; // rref1が右辺値参照[1] 1は右辺値 | |||
// int&& rref2 = x; // コンパイルエラー | |||
// rref2が右辺値参照 xは左辺値 | |||
// int&& rref3 = lref1; // コンパイルエラー | |||
// 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は左辺値 | |||
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> | |||
<br><br> | |||
== ムーブ == | |||
C++11以降、代入とコンストラクタにはコピーとムーブがある。<br> | |||
コピーは、データを全てコピーするため重い処理になるが、ムーブは、ポインタとサイズ情報のコピーのみを行うため軽い。<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> | <br> | ||
例えば、aの値をbに代入する時、インスタンスのコピーが行われて、メモリ上に2つのインスタンスが存在することになる。<br> | 例えば、aの値をbに代入する時、インスタンスのコピーが行われて、メモリ上に2つのインスタンスが存在することになる。<br> | ||
155行目: | 224行目: | ||
Counter c3(c1); // コピーコンストラクタ | Counter c3(c1); // コピーコンストラクタ | ||
Counter c4(std::move(c2)); // ムーブコンストラクタ | Counter c4(std::move(c2)); // ムーブコンストラクタ | ||
std::string str1 = "abc"; | |||
std::string str2 = str1; // str2をコピー | |||
std::string str3 = std::move(str1); // str3にstr1の内容をムーブする。以降str1の内容は不定となる | |||
std::string str4(std::move(str3)); // str4にstr3の内容をムーブする。以降str3の内容は不定となる | |||
return 0; | return 0; | ||
} | } | ||
</syntaxhighlight> | |||
<br><br> | |||
== ユニバーサル参照(&&で左辺値も束縛できる特別な例外) == | |||
<code>auto</code>変数や<code>template</code>変数の<code>&&</code>による参照は、右辺値だけでなく、左辺値も束縛できる。<br> | |||
<syntaxhighlight lang="c++"> | |||
// []は束縛対象(参照元), <>はコピーされた値 | |||
template <typename T> | |||
void g(T&& urefa) {} | |||
int x = 1; // xは左辺値 1は右辺値 | |||
int& lref1 = x; // lref1が左辺値参照 xは左辺値 | |||
const int& clref1 = x; // clref1はconst左辺値参照 xは左辺値 | |||
int&& rref1 = 1; // rref1が右辺値参照[1] 1は右辺値 | |||
// int&& rref2 = x; // コンパイルエラー | |||
// rref2が右辺値参照 lref1が左辺値参照 | |||
// int&& rref3 = lref1; // コンパイルエラー | |||
// rref3が右辺値参照 lref1が左辺値参照 | |||
// int&& rref4 = rref1; // コンパイルエラー | |||
// rref4が右辺値参照 rref1が右辺値参照(左辺値) | |||
// int&& rref5 = clref1; // コンパイルエラー | |||
// rref5が右辺値参照 clref1がconst左辺値参照 | |||
auto&& uref1 = 1; // uref1がユニバーサル参照[1] 1は右辺値 | |||
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は右辺値 | |||
g(x); // g()の引数はユニバーサル参照 xは左辺値 | |||
g(lref1); // g()の引数はユニバーサル参照 lref1は左辺値参照 | |||
g(rref1); // g()の引数はユニバーサル参照 rref1が右辺値参照 | |||
g(clref1); // g()の引数はユニバーサル参照 clref1がconst左辺値参照 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br><br> |
2021年3月12日 (金) 17:09時点における版
概要
C言語では、代入演算子=の左側にあるものを左辺値(lvalue)、右側(rvalue)にあるものを右辺値として決められている。
C++では、左辺値は名前を持つオブジェクト(&演算子でアドレスを取得できる)、右辺値は名前を持たない一時オブジェクトのことである。
ここでは、右辺値、左辺値、右辺値参照、左辺値参照、ムーブ、ユニバーサル参照、完全転送の意味を記載する。
右辺値と左辺値
メソッドの戻り値、式の結果等は右辺値である。
class X;
int func();
int main()
{
X(); // クラスのコンストラクタは右辺値
func(); // func関数の戻り値は右辺値
int i = 1; // 変数iは左辺値, 1は右辺値
i + 1; // 式i + 1は右辺値
}
右辺値は左辺値に変換できないため、以下はコンパイルエラーとなる。
int main()
{
int x; // 変数xは左辺値
(x + 1) = 9; // コンパイルエラー
}
左辺値とは、変数に代入されている値のことである。
class X;
int main()
{
int i; // 変数iは左辺値
X x; // Xクラスのインスタンスは左辺値
X *ptr_X = &x; // ポインタ変数は左辺値
}
以下に、右辺値と左辺値の比較を示す。
int x = 1; // xは左辺値、1は右辺値
int y = x + 1; // yは左辺値、式x + 1は右辺値、xは左辺値
int z = func(y + 2); // zは左辺値、func関数の戻り値は右辺値、式y + 2も右辺値
struct Point
{
int x = 0;
int y = 0;
};
Point pt = Point(); // Pointクラスのインスタンスptは左辺値、Pointクラスのコンストラクタは右辺値
1; // 1は右辺値
func(z); // func関数の戻り値は右辺値
// 戻り値はコピーも束縛もしていないため、以降使用できない
左辺値参照と右辺値参照
左辺値参照は、左辺値を束縛(※)すること、または、その参照変数のことである。
左辺値参照は右辺値を参照することができないが、constが付加された左辺値参照は右辺値を参照することができる。
※束縛
右辺値・左辺値に関わらず、参照を初期化することを束縛するという。
参照 = 束縛対象といった代入の形、または、束縛対象を関数の実引数にする形で束縛される。
参照を使用する時、束縛対象(参照元)の値を返す。
また、参照に参照を代入する時、代入元の束縛対象を代入先の束縛対象にする。
class X {};
void f(X &x) {}
void g(const X &x) {}
int main()
{
X x;
f(x); // コンパイル可能
// コピーコンストラクタの引数は左辺値参照
g(x); // コンパイルエラー
// Xクラスのインスタンスは左辺値のため、constが付加された引数には指定できない
g(X()); // コンパイル可能
// constが付加された引数は右辺値を参照できる
}
右辺値参照は、右辺値を束縛(※)すること、または、その参照変数のことである。
右辺値参照は、<データ型>&& <変数名>と記述する。
右辺値参照は型であるため、左辺値になることができる。
int func()
{
return 20;
}
int main()
{
int&& i = 10; // 右辺値10を右辺値参照変数iに束縛
int&& j = func(); // func関数の戻り値(右辺値)を右辺値参照変数jに束縛
std::cout << i << std::endl; // 10
std::cout << j << std::endl; // 20
i = j; // 右辺値参照は左辺値になることもできるため、コンパイル可能
std::cout << i << std::endl; // 20
std::cout << j << std::endl; // 20
}
以下に、左辺値参照と右辺値参照の比較を示す。
// []は束縛対象(参照元), <>はコピーされた値
int x = 1; // xは左辺値<1> 1は右辺値
int& lref1 = x; // lrefは左辺値参照[x] xは左辺値
int& lref2 = lref; // lref2は左辺値参照[x] lref1は左辺値参照[x]
// int& lref3 = 1; // コンパイルエラー
// lref3は左辺値参照 1は右辺値
int y = lref1; // yは左辺値<xの値> lrefは左辺値参照[x]
int&& rref1 = 1; // rref1が右辺値参照[1] 1は右辺値
// int&& rref2 = x; // コンパイルエラー
// rref2が右辺値参照 xは左辺値
// int&& rref3 = lref1; // コンパイルエラー
// rref3が右辺値参照 lrefは左辺値参照
// int&& rref4 = rref1; // コンパイエルエラー
// rref4が右辺値参照 rrefは右辺値参照
int&& rrefm = std::move(lref1); // rrefmが右辺値参照 std::move(lref1)は右辺値
int w = rref1; // wが左辺値<1> rref1は右辺値参照[1]
以下に、constを付加した左辺値参照も示す。
一般的に、constを付加した右辺値参照は使用されない。(constを付加したオブジェクトの内部を変更すべきではないため)
// []は束縛対象(参照元), <>はコピーされた値
int x = 1
const int &clref1 = x; // clref1がconst左辺値参照[1] xは左辺値
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]
ムーブ
C++11以降、代入とコンストラクタにはコピーとムーブがある。
コピーは、データを全てコピーするため重い処理になるが、ムーブは、ポインタとサイズ情報のコピーのみを行うため軽い。
ただし、ムーブ元のオブジェクトのデータは、ムーブ後は不定になるため、そのオブジェクトは使用できなくなる。
ムーブ先のオブジェクトがムーブに対応している場合、左辺値をstd::move
メソッドの引数に指定して、
代入、または、コンストラクタの引数に指定するだけでムーブができる。
特に、右辺値参照はムーブで使用されることが多い。
※std::moveメソッド
std::move
メソッドは、左辺値を右辺値にキャストする。
右辺値なので、代入した場合は、operartor=(const type&)
、ムーブオペレータoperartor=(type&&)
が使用される。
ムーブオペレータは、ポインタの挿げ替えとムーブ元オブジェクトを無効にする機能が実装されている。
例えば、aの値をbに代入する時、インスタンスのコピーが行われて、メモリ上に2つのインスタンスが存在することになる。
もし、aが指すインスタンスが以降で使用されない場合、aのポインタをbが持つだけでよいため、コピー操作は必要無い。
class Test;
Test a = Test();
Test b = a;
// aはこれ以降使用しない
ある変数が持つオブジェクトを別の変数に割り当てて、その変数からはオブジェクトを使わないようにする操作をムーブという。
ムーブは、ムーブコンストラクタやムーブ演算子を使用して表現する。
ムーブコンストラクタやムーブ演算子の引数に、右辺値参照が現れる。
ムーブに対して、コピー演算子やコピーコンストラクタの引数は、左辺値参照となる。
std::move
メソッドは、左辺値を右辺値にキャストするものである。
#include <iostream>
#include <utility>
class Counter
{
private:
int m_cnt;
public:
Counter() : m_cnt(0)
{
std::cout << "Default" << std::endl;
}
Counter(const Counter &c)
{
std::cout << "Copy" << std::endl;
}
Counter(Counter &&c)
{
m_cnt = c.getCnt();
std::cout << "Move" << std::endl;
}
~Counter() {}
int getCnt() {return m_cnt;}
};
int main()
{
Counter c1, // 引数無しのコンストラクタ
c2; // 引数無しのコンストラクタ
Counter c3(c1); // コピーコンストラクタ
Counter c4(std::move(c2)); // ムーブコンストラクタ
std::string str1 = "abc";
std::string str2 = str1; // str2をコピー
std::string str3 = std::move(str1); // str3にstr1の内容をムーブする。以降str1の内容は不定となる
std::string str4(std::move(str3)); // str4にstr3の内容をムーブする。以降str3の内容は不定となる
return 0;
}
ユニバーサル参照(&&で左辺値も束縛できる特別な例外)
auto
変数やtemplate
変数の&&
による参照は、右辺値だけでなく、左辺値も束縛できる。
// []は束縛対象(参照元), <>はコピーされた値
template <typename T>
void g(T&& urefa) {}
int x = 1; // xは左辺値 1は右辺値
int& lref1 = x; // lref1が左辺値参照 xは左辺値
const int& clref1 = x; // clref1はconst左辺値参照 xは左辺値
int&& rref1 = 1; // rref1が右辺値参照[1] 1は右辺値
// int&& rref2 = x; // コンパイルエラー
// rref2が右辺値参照 lref1が左辺値参照
// int&& rref3 = lref1; // コンパイルエラー
// rref3が右辺値参照 lref1が左辺値参照
// int&& rref4 = rref1; // コンパイルエラー
// rref4が右辺値参照 rref1が右辺値参照(左辺値)
// int&& rref5 = clref1; // コンパイルエラー
// rref5が右辺値参照 clref1がconst左辺値参照
auto&& uref1 = 1; // uref1がユニバーサル参照[1] 1は右辺値
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は右辺値
g(x); // g()の引数はユニバーサル参照 xは左辺値
g(lref1); // g()の引数はユニバーサル参照 lref1は左辺値参照
g(rref1); // g()の引数はユニバーサル参照 rref1が右辺値参照
g(clref1); // g()の引数はユニバーサル参照 clref1がconst左辺値参照