コンストラクタ周りの挙動確認

背景

コンストラクタ周りの挙動で不明点があったので改めて動きを確認した

出力結果

単なるクラスの定義

{
  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

参考

qiita.com

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