背景
コンストラクタ周りの挙動で不明点があったので改めて動きを確認した
出力結果
単なるクラスの定義
{ std::cout << "========= Start =========" << std::endl; MyClass m; std::cout << "========= End =========" << std::endl; }
コードブロックを出るまでオブジェクトが維持される。
========= Start ========= Constructor ========= End ========= Destructor
クラスを定義するが変数をつけず右辺値として定義
{ std::cout << "========= Start =========" << std::endl; MyClass(); std::cout << "========= End =========" << std::endl; }
クラスはすぐに破棄される
========= Start ========= Constructor Destructor ========= End =========
右辺値参照でクラスに変数をつける
{ std::cout << "========= Start =========" << std::endl; MyClass && m = MyClass(); std::cout << "========= End =========" << std::endl; }
コードブロックが終わるまで生き残る
========= Start ========= Constructor ========= End ========= Destructor
コピー的な定義(昔よくやっていた方法)
{ std::cout << "========= Start =========" << std::endl; MyClass m = MyClass(); std::cout << "========= End =========" << std::endl; }
2回コンストラクタが呼ばれると思いきや1回しか呼ばれない
========= Start ========= Constructor ========= End ========= Destructor
2つめのオブジェクトをコピーとして定義
{ std::cout << "========= Start =========" << std::endl; MyClass m1; MyClass m2 = m1; std::cout << "========= End =========" << std::endl; }
コピーコンストラクタが呼ばれる。
========= Start ========= Constructor Copy constructor ========= End ========= Destructor Destructor
2つめのオブジェクトを代入として定義
{ std::cout << "========= Start =========" << std::endl; MyClass m1; MyClass m2; m2 = m1; std::cout << "========= End =========" << std::endl; }
コピーアサイメントが呼ばれる。
========= Start ========= Constructor Constructor Copy assignment ========= End ========= Destructor Destructor
ムーブを使う
{ std::cout << "========= Start =========" << std::endl; MyClass m1; MyClass m2 = std::move(m1); // ここ以降m1の状態がどうなっているかは不定なのでm1を使ってはいけない std::cout << "========= End =========" << std::endl; }
ムーブコンストラクタが呼ばれる
========= Start ========= Constructor Move constructor ========= End ========= Destructor Destructor
参考
vectorに詰めるときの動きの確認
オブジェクトの定義の仕方
A: 一度変数に詰めてから渡す
MyClass myclass; func(myclass);
B: 一次オブジェクトとして渡す
func(MyClass());
関数の受け取り方
普通にvalueとして受け取る
void push_back_no_and(const MyClass a) { std::vector<MyClass> vec; vec.push_back(a); }
&を1つつけて受け取る
void push_back_single_and(const MyClass & a) { std::vector<MyClass> vec; vec.push_back(a); }
&を2つつけて右辺値として受け取る
引数の部分にconstをつけられないことに注意。
void push_back_double_and(MyClass && a) { // constつけちゃダメ std::vector<MyClass> vec; vec.push_back(std::move(a)); }
結果
受け取り方. | 一度変数に詰めてから渡す | 一次オブジェクトとして渡す |
---|---|---|
&なし | Const, Copy×2(関数に渡すときとvectorに詰めるときで2回コピーされる) | Const, Copy (一時オブジェクトを値渡しするときはコピーされない?) |
&1つ | Const, Copy (vectorに詰めるときのコピーのみ) | Const, Copy (vectorに詰めるときのコピーのみ) |
& 2つ | Const, Move (※) (ムーブなので効率が良い) | Const, Move (ムーブなので効率が良い) |
(※)
MyClass myclass; push_back_double_and(myclass);
だとエラーになるので
MyClass myclass; push_back_double_and(std::move(myclass));
としている。
テンプレートタイプをvectorに詰めるとき
template <typename T> void func(T value) { vec.push_back(static_cast<T>(val)); }
とすると、型がrvalueだったときはstd::move
したのと近い振る舞いとなりムーブセマンティクスが使える。
ソースコード全文
ソースコード展開
#include <iostream> #include <vector> #include <array> class MyClass { private: std::array<int, 10000> data; public: MyClass() { std::cout << "Constructor" << std::endl; } ~MyClass() { std::cout << "Destructor" << std::endl; } MyClass(const MyClass & a) { // this->data = a.data; std::cout << "Copy constructor" << std::endl; } MyClass(MyClass && a) { //this->data = std::move(a.data); std::cout << "Move constructor" << std::endl; } MyClass & operator=(const MyClass & a) { std::cout << "Copy assignment" << std::endl; return *this; } MyClass & operator=(MyClass && a) { std::cout << "Move assignment" << std::endl; return *this; } void PrintSize() { std::cout << data.size() << std::endl; } }; void push_back_no_and(const MyClass a) { std::vector<MyClass> vec; vec.push_back(a); } void push_back_single_and(const MyClass & a) { std::vector<MyClass> vec; vec.push_back(a); } void push_back_double_and(MyClass && a) { // constつけちゃダメ std::vector<MyClass> vec; vec.push_back(std::move(a)); } int main() { // Constructor / Destructor timing check { std::cout << "========= Start =========" << std::endl; MyClass m; std::cout << "========= End =========" << std::endl; } { std::cout << "========= Start =========" << std::endl; MyClass(); std::cout << "========= End =========" << std::endl; } { std::cout << "========= Start =========" << std::endl; MyClass && m = MyClass(); std::cout << "========= End =========" << std::endl; } { std::cout << "========= Start =========" << std::endl; MyClass m = MyClass(); std::cout << "========= End =========" << std::endl; } { std::cout << "========= Start =========" << std::endl; MyClass m1; MyClass m2 = m1; std::cout << "========= End =========" << std::endl; } { std::cout << "========= Start =========" << std::endl; MyClass m1; MyClass m2; m2 = m1; std::cout << "========= End =========" << std::endl; } { std::cout << "========= Start =========" << std::endl; MyClass m1; MyClass m2 = std::move(m1); // ここ以降m1の状態がどうなっているかは不定なのでm1を使ってはいけない std::cout << "========= End =========" << std::endl; } { std::cout << "1-A" << std::endl; MyClass myclass; push_back_no_and(myclass); std::cout << "1-B" << std::endl; push_back_no_and(MyClass()); } { std::cout << "2-A" << std::endl; MyClass myclass; push_back_single_and(myclass); std::cout << "2-B" << std::endl; push_back_single_and(MyClass()); } { std::cout << "3-A" << std::endl; MyClass myclass; //push_back_double_and(myclass); // error push_back_double_and(std::move(myclass)); std::cout << "3-B" << std::endl; push_back_double_and(MyClass()); } return 0; }
参考
c++ - is there any difference between static cast to rvalue reference and std::move - Stack Overflow