1. ホーム
  2. C++

c++ std::move Principle の実装と使用法のまとめ

2022-02-09 04:43:51

C++11では、標準ライブラリは<utility>に便利な関数std::moveを提供しています。std::moveは何も移動しません。その唯一の機能は、左値を右参照に強制変換し、右参照による移動セマンティクスに使用できるようにすることです。実装としては、std::move は基本的に static_cast<T&&>(lvalue) という型変換と等価です。

std::move関数は、非常に簡単な方法で左値参照を右値参照に変換します。(左値右値参照左値参照)の概念  https://blog.csdn.net/p942005405/article/details/84644101

  1. C++標準ライブラリでは、vector::push_backなどの関数を使って、引数のオブジェクトをコピーし、データもコピーしています。そのため、引数をpush_backするだけでよかったはずのオブジェクトメモリが余分に生成されてしまいますが、std::moveを使うことで不要なコピーを回避することができます。
  2. std::moveはオブジェクトの状態や所有権をあるオブジェクトから別のオブジェクトに転送するもので、単なる転送であり、メモリの再配置やメモリコピーは行わないので、より効率的に使用でき、パフォーマンスを向上させることができます。
  3. ポインタ型の標準ライブラリオブジェクトには必要ありません。

使用方法

元のlvalueの値から移動するので、空文字列になります。 

//from https://zh.cppreference.com/w/cpp/utility/move
#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
    // call the regular copy constructor, create a new array of characters, and copy the data
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n";
    // Call the move constructor, hollow out str, and after hollowing out, it is better not to use str
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";
    std::cout << "The contents of the vector are \"" << << v[0]
                                         << "\", \"" << v[1] << "\"\n";
}

出力します。

After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"


std::move の関数プロトタイプ定義

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);


プロトタイプ定義における模式的な実装。

 まず、関数の引数T&&は、テンプレート型の引数への右値参照である。参照畳み込みによって、この引数はどんなタイプの実参照にもマッチする(左値でも右値でも渡すことができ、これはstd::moveが使われる2つの主なシナリオである)。参照フォールディングについては、以下の通りである。

      (式1) X& &, X&& &, X& & は左の値では X& に折りたたまれます。

string s("hello");
std::move(s) => std::move(string& &&) => after folding std::move(string& )
At this point: the type of T is string&
typename remove_reference<T>::type is string 
The entire std::move is instantiated as follows
string&& move(string& t) //t is the left value, and cannot be used after moving t
{
    //Forced conversion of string& to string&& by static_cast;
    return static_cast<string&&>(t); 
}





      (式2) X&&&は正しい値ではX&&に縮退されます。

std::move(string("hello")) => std::move(string&&)
// At this point: the type of T is string 
// remove_reference<T>::type for string 
// The whole std::move is instantiated as follows
string&& move(string&& t) // t is the right value
{
    return static_cast<string&&>(t); //return a right-valued reference
}





簡単に言うと、T&&を通過する右値は右値と同じ型のままで、T&&を通過する左値は通常の左参照となる。

static_cast<>を使用する際の注意点。明示的に定義された任意の型変換は、基礎となる const を含まない限り、static_cast を使用できます。

double d = 1;
void* p = &d;
double *dp = static_cast<double*> p; //correct
 
const char *cp = "hello";
char *q = static_cast<char*>(cp); //error: static cannot remove the const property
static_cast<string>(cp); //correct 





remove_reference は、以下のコードでクラステンプレートを部分的に特殊化することで実装しています。

// The original, most generic version
template <typename T> struct remove_reference{
    typedef T type; //define the type alias of T as type
};
 
//part of the version specialization that will be used for left-valued references and right-valued references
template <class T> struct remove_reference<T&> //left-valued reference
{ typedef T type; }
 
template <class T> struct remove_reference<T&&> //right-valued reference
{ typedef T type; }   
  
// For example, the following three variables defined as a, b, and c are all int types
int i;
remove_refrence<decltype(42)>::type a; //use the original version
remove_refrence<decltype(i)>::type b; //left-valued reference to the special case version
remove_refrence<decltype(std::move(i))>::type b; //right-valued reference to the special case version 





要約すると

std::move の実装は,まず,右値の参照を渡し,参照折りたたみの原理を利用して,T&& を通して右値を渡すことで型を変更しないか右値化し,T&& を通して左値は通常の左値参照となることでテンプレートが任意の実参照を渡して型を変更しないようにできるようにしています.そして,static_cast<>によって強制的に型変換を行い,T&&の右値参照を返し,static_cast<T>によって型変換できるのは,remove_reference<T&t;::型テンプレート ;, T&references によって T&& を取り除き具体型 T にするからです.


参考リンク

https://blog.csdn.net/fengbingchun/article/details/52558914 

https://blog.csdn.net/cpriluke/article/details/79462388 

https://blog.csdn.net/swartz_lubel/article/details/59620868