- それは何ですか?
- それは何をするためのものか?
- いつ使用する必要がありますか?
良いリンクをいただければ幸いです。
良いリンクをいただければ幸いです。
std::move()
技術的には関数ですが、実際には関数ではないと思います。これは、コンパイラが式の値を考慮する方法間の一種のコンバータです。
最初に注意することは、std::move()
実際には何も動かさないということです。式を左辺値(名前付き変数など)からx値に変更します。xvalueは、コンパイラーに次のことを通知します。
あなたは私を略奪し、私が持っているものを何でも動かして、他の場所でそれを使うことができます(とにかく私はすぐに破壊されるでしょうから)」。
つまり、を使用するstd::move(x)
と、コンパイラが共食いできるようになりますx
。したがってx
、たとえば、メモリ内に独自のバッファがある場合std::move()
、コンパイラは、ing後、代わりに別のオブジェクトにそれを所有させることができます。
prvalue (一時的なものなど)から移動することもできますが、これが役立つことはめったにありません。
この質問をする別の方法は、「既存のオブジェクトのリソースを何のために共食いするのか」です。ええと、アプリケーションコードを書いているのなら、コンパイラによって作成された一時オブジェクトをいじり回すことはおそらくないでしょう。したがって、主にこれは、コンストラクター、演算子メソッド、標準ライブラリアルゴリズムのような関数など、オブジェクトが自動的に作成および破棄される場所で行います。もちろん、それは経験則です。
典型的な使用法は、コピーする代わりに、あるオブジェクトから別のオブジェクトにリソースを「移動」することです。@Guillaumeは、このページにリンクしています。このページには、簡単な短い例があります。コピーを減らして2つのオブジェクトを交換します。
template <class T>
swap(T& a, T& b) {
T tmp(a); // we now have two copies of a
a = b; // we now have two copies of b (+ discarded a copy of a)
b = tmp; // we now have two copies of tmp (+ discarded a copy of b)
}
moveを使用すると、リソースをコピーする代わりに、リソースを交換できます。
template <class T>
swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
T
たとえば、vector<int>
サイズがnの場合に何が起こるかを考えてください。最初のバージョンでは、3 * n要素を読み書きします。2番目のバージョンでは、基本的に、ベクトルのバッファーへの3つのポインターと、3つのバッファーのサイズだけを読み書きします。もちろん、クラスT
は移動の方法を知っている必要があります。これを機能させるには、クラスにムーブ代入演算子とクラスのムーブコンストラクターが必要T
です。
C++11のR値参照と移動コンストラクターに関するウィキペディアのページ
Type &&
)の場合、コピーコンストラクターの代わりに移動コンストラクターが使用されます。std::move()
オブジェクトからの移動を可能にするために、オブジェクトへの右辺値参照を生成するキャストです。これは、コピーを回避するための新しいC++の方法です。たとえば、moveコンストラクターを使用すると、std::vector
データへの内部ポインターを新しいオブジェクトにコピーするだけで、移動したオブジェクトをmoved from状態のままにして、すべてのデータをコピーすることはできません。これはC++で有効になります。
移動セマンティクス、右辺値、完全な転送についてグーグルで試してください。
コピーを行わずに、オブジェクトのコンテンツを別の場所に「転送」する必要がある場合は、moveを使用できます(つまり、コンテンツが複製されないため、unique_ptrなどのコピー不可能なオブジェクトで使用できます)。std :: moveを使用すると、オブジェクトがコピーを実行せずに一時オブジェクトのコンテンツを取得する(そして多くの時間を節約する)ことも可能です。
このリンクは本当に私を助けました:
http://thbecker.net/articles/rvalue_references/section_01.html
回答が遅すぎて申し訳ありませんが、std :: moveの適切なリンクも探していたところ、上のリンクが少し「オーステリー」であることがわかりました。
これは、r値の参照に重点を置いており、どのコンテキストでそれらを使用する必要があるか、そしてそれがより詳細であると思うので、ここでこのリンクを共有したかったのです。
std::move
ですか?A:std::move()
右辺値参照にキャストするためのC++標準ライブラリの関数です。
単純std::move(t)
に次と同等です。
static_cast<T&&>(t);
右辺値は、変数に格納されることのない中間関数の結果など、右辺値を定義する式を超えて存続しない一時的なものです。
int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated
std :: move()の実装は、N2027:「右辺値参照の簡単な紹介」に次のように記載されています。
template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
return a;
}
ご覧のとおり、値( )、参照型()、または右辺値参照( )で呼び出されても、をstd::move
返します。T&&
T
T&
T&&
A:キャストとして、実行時には何もしません。コンパイル時にのみ、参照を右辺値と見なし続けることをコンパイラーに通知することが重要です。
foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)
int a = 3 * 5;
foo(a); // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`
それがしないこと:
A:std::move
右辺値(一時的な式)ではない引数を使用して、移動セマンティクスをサポートする関数を呼び出す場合に使用する必要があります。
これは私に次のフォローアップの質問をします:
移動セマンティクスとは何ですか?コピーセマンティクスとは対照的に、移動セマンティクスは、別のオブジェクトのメンバーをコピーする代わりに、オブジェクトのメンバーが「引き継ぐ」ことによって初期化されるプログラミング手法です。このような「引き継ぎ」は、ポインタとリソースハンドルでのみ意味があります。これらは、基になるデータではなく、ポインタまたは整数ハンドルをコピーすることで安価に転送できます。
どのような種類のクラスとオブジェクトが移動セマンティクスをサポートしていますか?メンバーをコピーする代わりに転送することでメリットが得られる場合は、開発者が独自のクラスに移動セマンティクスを実装する必要があります。移動セマンティクスを実装すると、移動セマンティクスを使用してクラスを効率的に処理するためのサポートを追加した多くのライブラリプログラマーからの作業から直接利益を得ることができます。
コンパイラがそれを自分で理解できないのはなぜですか?あなたがそう言わない限り、コンパイラは関数の別のオーバーロードを呼び出すことはできません。関数の通常バージョンとmoveバージョンのどちらを呼び出すかをコンパイラーが選択できるようにする必要があります。
どのような状況で、変数を右辺値として扱う必要があることをコンパイラーに伝えたいですか?これは、(新しいインスタンスを割り当てるのではなく)中間結果が回収される可能性があることがわかっているテンプレートまたはライブラリ関数で発生する可能性があります。
std::move自体は実際にはあまり効果がありません。オブジェクトの移動コンストラクターを呼び出すと思いましたが、実際には型キャストを実行するだけです(左辺値変数を右辺値にキャストして、その変数を引数として移動コンストラクターまたは代入演算子に渡すことができるようにします)。
したがって、std :: moveは、moveセマンティクスを使用する前兆として使用されます。移動セマンティクスは、基本的に一時オブジェクトを処理するための効率的な方法です。
オブジェクトを検討するA = B + (C + (D + (E + F)));
これは見栄えの良いコードですが、E+Fは一時オブジェクトを生成します。次に、D+tempは別の一時オブジェクトを生成します。クラスの通常の「+」演算子ごとに、深いコピーが発生します。
例えば
Object Object::operator+ (const Object& rhs) {
Object temp (*this);
// logic for adding
return temp;
}
この関数での一時オブジェクトの作成は役に立ちません。これらの一時オブジェクトは、スコープ外になると、とにかく行の終わりで削除されます。
むしろ、移動セマンティクスを使用して一時オブジェクトを「略奪」し、次のようなことを行うことができます。
Object& Object::operator+ (Object&& rhs) {
// logic to modify rhs directly
return rhs;
}
これにより、不必要なディープコピーが作成されるのを防ぎます。例を参照すると、ディープコピーが発生するのはE+Fだけです。残りは移動セマンティクスを使用します。結果をAに割り当てるには、移動コンストラクターまたは代入演算子も実装する必要があります。
"それは何ですか?" と 「それは何をしますか?」上で説明されています。
「いつ使うべきか」の例をあげましょう。
たとえば、大きな配列のようなリソースがたくさんあるクラスがあります。
class ResHeavy{ // ResHeavy means heavy resource
public:
ResHeavy(int len=10):_upInt(new int[len]),_len(len){
cout<<"default ctor"<<endl;
}
ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
cout<<"copy ctor"<<endl;
}
ResHeavy& operator=(const ResHeavy& rhs){
_upInt.reset(new int[rhs._len]);
_len = rhs._len;
cout<<"operator= ctor"<<endl;
}
ResHeavy(ResHeavy&& rhs){
_upInt = std::move(rhs._upInt);
_len = rhs._len;
rhs._len = 0;
cout<<"move ctor"<<endl;
}
// check array valid
bool is_up_valid(){
return _upInt != nullptr;
}
private:
std::unique_ptr<int[]> _upInt; // heavy array resource
int _len; // length of int array
};
テストコード:
void test_std_move2(){
ResHeavy rh; // only one int[]
// operator rh
// after some operator of rh, it becomes no-use
// transform it to other object
ResHeavy rh2 = std::move(rh); // rh becomes invalid
// show rh, rh2 it valid
if(rh.is_up_valid())
cout<<"rh valid"<<endl;
else
cout<<"rh invalid"<<endl;
if(rh2.is_up_valid())
cout<<"rh2 valid"<<endl;
else
cout<<"rh2 invalid"<<endl;
// new ResHeavy object, created by copy ctor
ResHeavy rh3(rh2); // two copy of int[]
if(rh3.is_up_valid())
cout<<"rh3 valid"<<endl;
else
cout<<"rh3 invalid"<<endl;
}
以下のように出力します。
default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid
std::move
withを使用するとmove constructor
、リソースを簡単に変換できることがわかります。
他にどこがstd::move
便利ですか?
std::move
要素の配列を並べ替えるときにも役立ちます。多くの並べ替えアルゴリズム(選択並べ替えやバブル並べ替えなど)は、要素のペアを交換することで機能します。以前は、スワッピングを行うためにコピーセマンティクスに頼らなければなりませんでした。これで、より効率的な移動セマンティクスを使用できます。
また、あるスマートポインタで管理されているコンテンツを別のスマートポインタに移動する場合にも役立ちます。
引用:
std::move
それ自体は、よりも何もしませんstatic_cast
。cppreference.comによると
これは、右辺値参照型へのstatic_castとまったく同じです。
したがって、の後に割り当てる変数のタイプによって異なります。タイプにmove
右辺値パラメーターがある場合、constructors
またはassign operators
それが右辺値パラメーターを受け取る場合は、元の変数の内容を盗む場合と盗まない場合があるため、元の変数をそのままにしておくことができます。でunspecified state
:
特に指定のない限り、移動されたすべての標準ライブラリオブジェクトは、有効であるが指定されていない状態になります。
整数やrawポインタなどの特別なmove constructor
、またはmove assign operator
組み込みのリテラルタイプはないため、これらのタイプの単純なコピーになります。
これは、(単純な)カスタムベクトルにstd::moveを使用した完全な例です。
期待される出力:
c: [10][11]
copy ctor called
copy of c: [10][11]
move ctor called
moved c: [10][11]
次のようにコンパイルします。
g++ -std=c++2a -O2 -Wall -pedantic foo.cpp
コード:
#include <iostream>
#include <algorithm>
template<class T> class MyVector {
private:
T *data;
size_t maxlen;
size_t currlen;
public:
MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }
MyVector<T> (const MyVector& o) {
std::cout << "copy ctor called" << std::endl;
data = new T [o.maxlen];
maxlen = o.maxlen;
currlen = o.currlen;
std::copy(o.data, o.data + o.maxlen, data);
}
MyVector<T> (const MyVector<T>&& o) {
std::cout << "move ctor called" << std::endl;
data = o.data;
maxlen = o.maxlen;
currlen = o.currlen;
}
void push_back (const T& i) {
if (currlen >= maxlen) {
maxlen *= 2;
auto newdata = new T [maxlen];
std::copy(data, data + currlen, newdata);
if (data) {
delete[] data;
}
data = newdata;
}
data[currlen++] = i;
}
friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
auto s = o.data;
auto e = o.data + o.currlen;;
while (s < e) {
os << "[" << *s << "]";
s++;
}
return os;
}
};
int main() {
auto c = new MyVector<int>(1);
c->push_back(10);
c->push_back(11);
std::cout << "c: " << *c << std::endl;
auto d = *c;
std::cout << "copy of c: " << d << std::endl;
auto e = std::move(*c);
delete c;
std::cout << "moved c: " << e << std::endl;
}