左辺値と右辺値
C言語では、一般に左辺値と右辺値を区別する方法が2つあります。代入記号「=」の両側に置ける値を左辺値、代入記号「=」の右辺にしか置けない値を右辺値と言い、アドレスを取ることが左辺値であり、アドレスを取ることができないものは右辺値です。しかし、これらの 2 つのステートメントは完全に正しいわけではありません
void test()
{
int a = 10;
int b = a;//ok a为左值
10 = a; //error 10为右值
int* pa = &a;//ok
int* pi = &10;//error
}
C++ では、定数、一時変数/無名変数、xvaluesの 3 種類の右辺値の変数があり、その他の変数は左辺値です。
瀕死の値は、宣言がもうすぐ終了する変数です
int fun(int a)
{
return a;//将亡值
}
C++ が右辺値を導入する理由
- 移動セマンティクスの実装 (移動の構築と移動の割り当て)
- 中間一時変数に名前を付ける
- 完全な転送を達成する
左辺値参照と右辺値参照
左辺値参照は通常使用する参照であり、左辺値参照は type の後に追加され&
、変数は参照型として定義されます。左辺値を参照し、左辺値と右辺値を参照できます
void test()
{
int a = 10;
int& ra = a;//引用左值
const int& ri = 10;//引用右值
}
変数 type に右辺値参照を追加する必要があり&&
、これは右辺値参照です。右辺値参照は左辺値を参照できず、右辺値のみを参照できます
void test()
{
int a = 10;
int&& rri = 10;//ok
int&& rra = a;//error;
}
右辺値参照の役割
私たちが普段書いているコードでは、コピーを作成したりスペースを解放したりするオーバーヘッドが発生することが多く、C++ は極端なパフォーマンスを追求する言語であるため、元の機能を維持しながらパフォーマンスを向上させるために最善を尽くします。右辺値参照を導入する目的は、いくつかの特定のシナリオでコード操作の効率を向上させることです。ディープ コピーを実行しないことでコード コピーの効率を向上させます。
まず、実装した文字列クラスを見てみましょう。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class String {
public:
String(const char* str = "") {
if (nullptr == str)
str = "";
_str = new char[strlen(str) + 1]; strcpy(_str, str);
}
String(const String& s) :
_str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
cout << "String(const String&)" << endl;
}
String& operator=(const String& s) {
if (this != &s) {
char* pTemp = new char[strlen(s._str) + 1];
strcpy(pTemp, s._str);
delete[] _str; _str = pTemp;
cout << "String& operator=(const String&)" << endl;
} return *this;
}
String operator+(const String& s) {
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
~String()
{
if (_str) delete[] _str;
}
private: char* _str;
};
void test() {
String s1("hello");
String s2("world");
String s3(s1 + s2);
}
テストコードでは s3 を作成するのに何度もスペースを申請する. 1つ目は s1 と s2 に + 演算を行う. operator+ 関数では strRet にスペースを代入するとコピーされ, then in strRet が返されたときにもコピーが実行され、コピーされた一時変数を使用して s3 を作成するときにコピーの構築が呼び出されます。コピーの構築はディープ コピーであり、各呼び出しはスペースを開く必要があり、時間がかかります
が、vs のコンパイラはそれを最適化します。戻り値をコピーするステップを削除
しかし、C++11 では右辺値参照が導入されており、最適化の余地があります。つまり、s3 は新しいスペースを開く必要はなく、strRet の元のリソースを直接使用します。strRet のリソースを s3 に移動します
String(String&& s)
:_str(s._str)//直接指向strRet的资源
{
s._str = nullptr;//置其为空,防止二次释放
cout << "String(String&&)" << endl;
}
右辺値参照のコピー構築では、新しいスペースは割り当てられません。これら 2 つのコピー構成は共存できます. ほとんどの場合, 左辺値参照のコピー構成が呼び出されます. 上記の場合のみ, つまり, 一時変数は右辺値である場合にのみ右辺値参照のコピー構成を呼び出します.
同様に、オーバーロードされた代入演算子も右辺値参照を使用できます
String& operator=(String&& s) {
if (this != &s) {
delete[] _str;
_str = s._str;
s._str = nullptr;
cout << "String& operator=(String&&)" << endl;
} return *this;
}
move
---- 左辺値属性の変数を右辺値に変更します
void test()
{
int a = 10;
int&& rra = move(a);//ok
}
完全な転送- 関数の転送中に変数の元の属性を保持します
void Fun(int& x) {
cout << "左值引用" << endl; }
void Fun(int&& x) {
cout << "右值引用" << endl; }
void Fun(const int& x) {
cout << "const类型的左值引用" << endl; }
void Fun(const int&& x) {
cout << "const类型的右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) //如果是左值则为左值引用
{
Fun(std::forward<T>(t)); //完美转发std::forward<T>(name)
}
void test() {
PerfectForward(10); // 右值引用
int a = 4;
PerfectForward(a); // 左值引用
PerfectForward(std::move(a)); // 右值引用
const int b = 8;
PerfectForward(b); // const类型的左值引用
PerfectForward(std::move(b)); // const类型的右值引用
}