C++ C++11 の新機能 -- 右辺値リファレンス

左辺値と右辺値

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++ が右辺値を導入する理由

  1. 移動セマンティクスの実装 (移動の構築と移動の割り当て)
  2. 中間一時変数に名前を付ける
  3. 完全な転送を達成する

左辺値参照と右辺値参照

左辺値参照は通常使用する参照であり、左辺値参照は 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类型的右值引用
}

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_44443986/article/details/117405080