C++11右参照&&


一、新しい特性の目的
右参照(Rvalue Reference)は、C++の新しい規格(C++11、11は2011年を表す)に導入された新しい特性であり、移動の意味(Move Sementics)と正確な伝達(Perfect Forwarding)を実現している.主な目的は2つあります.
  • は、2つのオブジェクトが相互作用するときに不要なオブジェクトコピーを除去し、演算ストレージリソースを節約し、効率を向上させる.
  • は、汎用関数をより簡潔かつ明確に定義することができる.

  • 二、右とは何か
    C++(Cを含む)のすべての式と変数は、左または右のいずれかです.一般的な左値の定義は、複数の文で使用できる非一時オブジェクトです.すべての変数はこの定義を満たし、複数のコードで使用できます.左です.右の値は、現在の文でのみ有効な一時的なオブジェクトです.
    int i = 0;  //       ,i    ,0     ,    。
    

    C++11の前に、右の値は参照できません.たとえば、次のようにします.
    int &a = 1;   // error C2440: “   ”:    “int”   “int &”
    

    右の値をバインドするには、定数参照のみを使用します.たとえば、次のようにします.
    const int &a = 1;
    

    C++11では、&&を使用して右の値を参照して実装できます.
    int &&a = 1;
    

    三、シーンの適用
    コピーコンストラクション関数と付与演算子の再ロードを実現するstringクラスがあります.
    class MyString {
    private:
    	char* _data;
    	size_t   _len;
    	void _init_data(const char *s) {
    		_data = new char[_len + 1];
    		memcpy(_data, s, _len);
    		_data[_len] = '\0';
    	}
    public:
    	MyString() {
    		_data = NULL;
    		_len = 0;
    	}
    
    	MyString(const char* p) {
    		_len = strlen(p);
    		_init_data(p);
    	}
    
    	MyString(const MyString& str) {
    		_len = str._len;
    		_init_data(str._data);
    		std::cout << "Copy Constructor is called! source: " << str._data << std::endl;
    	}
    
    	MyString& operator=(const MyString& str) {
    		if (this != &str) {
    			_len = str._len;
    			_init_data(str._data);
    		}
    		std::cout << "Copy Assignment is called! source: " << str._data << std::endl;
    		return *this;
    	}
    
    	virtual ~MyString() {
    		if (_data != NULL) {
    			std::cout << "Destructor is called! " << std::endl; 
    			free(_data);
    		}
    	}
    };
    
    int main() { 
    	MyString a; 
    	a = MyString("Hello"); 
    	std::vector vec; 
    	vec.push_back(MyString("World")); 
    }
    

    実行結果:
    Copy Assignment is called! source: Hello
    Destructor is called!
    Copy Constructor is called! source: World
    Destructor is called!
    Destructor is called!
    Destructor is called!
    

    合計2回のコピーが実行され、MyString("Hello")およびMyString("World")は一時オブジェクトであり、一時オブジェクトが使用されるとすぐにプロファイルされ、プロファイル関数で申請されたメモリリソースがfreeされます.一時オブジェクトが既に申請したリソースを直接使用し、その構造関数でリソースの解放を取り消すことができれば、リソースを節約でき、リソースの申請と解放の時間を節約できます.これが転送の意味を定義する目的です.
    定義 および を追加することによって、右値参照(すなわち、一時オブジェクトの多重化)が実現される.
    	MyString(MyString&& str) { 
    		std::cout << "Move Constructor is called! source: " << str._data << std::endl; 
    		_len = str._len; 
    		_data = str._data; 
    		str._len = 0; 
    		str._data = NULL;   // !               
    	}
    
    	MyString& operator=(MyString&& str) { 
    		std::cout << "Move Assignment is called! source: " << str._data << std::endl; 
    		if (this != &str) { 
    			_len = str._len; 
    			_data = str._data; 
    			str._len = 0; 
    			str._data = NULL;  // !               
    		} 
    		return *this; 
    	}
    

    実行結果:
    Move Assignment is called! source: Hello
    Move Constructor is called! source: World
    Destructor is called!
    Destructor is called!
    

    右の値参照は、コンパイラが一時オブジェクトの使用後に解放されることを阻止することはできないので、 および では_dataNULLに割り当てられ、構造関数では_data != NULLが解放されることが保証されています.
    四、標準ライブラリ関数std::move
    コンパイラは、 および を呼び出すには右の値のみを参照する必要があります.また、すべてのネーミングオブジェクトが左の値のみを参照できるためです.このような条件では、名前付きオブジェクトが使用されなくなったことが知られている場合、転送構造関数と転送付与関数を呼び出したい場合、つまり左の値参照を右の値参照として使用します.どうすればいいですか.標準ライブラリには、左の参照を右の参照に変換する非常に簡単な方法で関数std::moveが用意されています.
    void ProcessValue(int& i) { 
    	std::cout << "LValue processed: " << i << std::endl; 
    } 
    
    void ProcessValue(int&& i) { 
    	std::cout << "RValue processed: " << i << std::endl; 
    } 
    
    int main() { 
    	int a = 0; 
    	ProcessValue(a); 
    	ProcessValue(std::move(a)); 
    }
    

    実行結果:
    LValue processed: 0 
    RValue processed: 0
    

    std::moveはswap関数の性能を向上させる上で非常に役立ち、一般的にswap関数の一般的な定義は以下の通りである.
    template  
    void swap(T& a, T& b) 
    { 
    	T tmp(a);   // copy a to tmp 
    	a = b;      // copy b to a 
    	b = tmp;    // copy tmp to b 
    }
    

    std::moveがあれば、右の値の参照を組み合わせると、不要なコピーを避けることができます.swap関数の定義は次のようになります.
    template 
    void swap(T& a, T& b) 
    { 
    	T tmp(std::move(a)); // move a to tmp 
    	a = std::move(b);    // move b to a 
    	b = std::move(tmp);  // move tmp to b 
    }
    

    第3節のMyStringクラスを使用してテストできます.
    int main() { 
    	MyString a("a");
    	MyString b("b");
    
    	swap(a, b);
    
    	return 0;
    }
    

    五、正確な伝達(Perfect Forwarding)
    正確な伝達とは、パラメータ伝達中にこれらの属性とパラメータ値を変更できないことです.汎用関数では,このような需要は非常に普遍的である.例を挙げて説明したほうが分かりやすい.
    forward_value関数にはパラメータvalが1つしかありません.定義は次のとおりです.
    template  
    void forward_value(const T& val) { 
    	process_value(val); 
    } 
    
    template  
    void forward_value(T& val) { 
    	process_value(val); 
    }
    

    関数forward_valueは、各パラメータに対して2つのタイプ、T&とconst T&を再ロードする必要があります.そうしないと、次の4つの異なるタイプのパラメータの呼び出しで同時に満たすことはできません.
    int a = 0; 
    const int &b = 1; 
    forward_value(a); // int& 
    forward_value(b); // const int& 
    forward_value(2); // const int&
    

    1つのパラメータに対して2回再ロードされます.すなわち、関数の再ロード回数とパラメータの個数は比例します.この関数の定義回数はプログラマーにとって非常に非効率である.右の値の参照がこの問題を解決する方法を見てみましょう.
    template  
    void forward_value(T&& val) { 
    	process_value(val); 
    }
    

    一度定義して、右の値で参照されるパラメータを1つ受け入れるだけで、すべてのパラメータタイプをそのままターゲット関数に渡すことができます.
    テストにより、VS 2015は右値参照&&&をサポートしています.
    参照先:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html