ムーブコンストラクタがどういう動きをするのか調べるために下記のようなコードを書いてみた。
まずムーブコンストラクタなしの場合。
#include <iostream>
#include <vector>
using namespace std;
struct Obj
{
Obj()
{
cout << "constructor" << endl;
}
Obj(const Obj& o)
{
cout << "copy constructor" << endl;
}
/* ムーブコンストラクタなしの場合
Obj(Obj&& o)
{
cout << "move constructor" << endl;
}
*/
};
int main() {
std::vector<Obj> v;
cout << "1\n";
v.push_back(Obj());
cout << "2\n";
v.push_back(Obj());
cout << "3\n";
v.push_back(Obj());
return 0;
}
コンソール出力
1
constructor
copy constructor
2
constructor
copy constructor
copy constructor
3
constructor
copy constructor
copy constructor
copy constructor
push_back のたびにコピーコンストラクタが呼ばれている。
多めにコピーコンストラクタが呼ばれるのは vector のリサイズのときにメモリの再確保&コピーが行われているため。
ムーブコンストラクタを追加してみると、
コンソール出力
1
constructor
move constructor
2
constructor
move constructor
copy constructor
3
constructor
move constructor
copy constructor
copy constructor
push_back のときにムーブコンストラクタが呼ばれるようになった。
しかし、vector の再確保&コピーのときには相変わらずコピーコンストラクタが呼ばれている。
そのため、ムーブコンストラクタの恩恵を十分に生かすにはあらかじめ reserve しておいた方が良さそうだ。
最終的なコード
#include <iostream>
#include <vector>
using namespace std;
struct Obj
{
Obj()
{
cout << "constructor" << endl;
}
Obj(const Obj& o)
{
cout << "copy constructor" << endl;
}
// ムーブコンストラクタありの場合
Obj(Obj&& o)
{
cout << "move constructor" << endl;
}
};
int main() {
std::vector<Obj> v;
// v に予め容量を確保
v.reserve(3);
cout << "1\n";
v.push_back(Obj());
cout << "2\n";
v.push_back(Obj());
cout << "3\n";
v.push_back(Obj());
return 0;
}
コンソール出力
1
constructor
move constructor
2
constructor
move constructor
3
constructor
move constructor
コンストラクタ & ムーブコンストラクタだけになった。
追記
頂いたコメントをもとに内容を加筆。
std::vector の push_back 時にコピーコンストラクタが呼ばれてしまう原因は、自作クラスのムーブコンストラクタに noexcept 修飾子を付けていなかったことが原因だったようだ。
std::vector は push_back の内部で std::move_if_noexcept() を使って要素を移動している。
この std::move_if_noexcept() がどのような動作をするかというと、
- ムーブコンストラクタに noexcept 修飾子を付がついている(=「このコンストラクタは例外を投げないよ」とコンパイラに明示している)ときには右辺値参照&&をリターン
- noexcept 修飾子がついていないときは const 左辺値参照をリターン
とのこと。
上記の自作クラスの例では、ムーブコンストラクタに noexcept 修飾子がついていないために const 参照が返され、コピーコンストラクタが呼ばれてしまっていた。
参考
cpprefjp:move_if_noexcept (C++11)
そこで、クラスを下記のように修正した
struct Obj
{
/* ... */
// ムーブコンストラクタに noexcept を付ける
Obj(Obj&& o) noexcept
{
cout << "move constructor" << endl;
}
};
noexcept を付けてあげると、下記のようにコピーコンストラクタが呼ばれていたところでムーブコンストラクタが呼ばれるようになった。
1
constructor
move constructor
2
constructor
move constructor
move constructor
3
constructor
move constructor
move constructor
move constructor
これで安心して push_back が使える。