#5 C++が自動で生成する関数(デフォルトコンストラクタ,コピーコンストラクタ,コピー代入演算子,デストラクタ)

今回の内容は Effective C++, 5項のまとめです.

クラスに何も記述しないような実装をしても,C++によって自動で生成される関数があります.それが,タイトルの通り,,,

です.

これは,Efective C++ の例そのままになってしまいますが,下記のようなクラス宣言をした場合,実際には下記のようなメソッドが生成されます.

下記のように何も実装しなくても....

class Empty() {};

実際には下記のようなメソッドが生成されている.

class Empty() {
public:
  // Default Constructor
  Empty(){}

  // Default Destructor
  ~Empty(){}

  // Default Copy Constructor
  Empty(const Empty &rhs) { ... }

  // Default Copy Operator
  Empty& operator=(const Empty&rhs) { ... }
};
1.コンストラク

デフォルトで生成されるコンストラクタは,下記のような特徴を持ってます.

  • 引数なし
  • 基底クラス・データメンバのコンストラクタのコールしてくれる.
  • ユーザが定義したコンストラクタが一つでもあると,生成されない.

サンプルコードを書いてみましたが,実際に Low のオブジェクトを生成(=デフォルトコンストラクタ)を呼んだタイミングで親クラスとデータメンバのコンストラクタが呼ばれていることがわかります.

#include <iostream>


class Top {
  public:
  Top();
};

Top::Top() {
  std::cout << "Top Constructor!" << std::endl;
}

class Middle {
public:
  Middle();
};

Middle::Middle() {
  std::cout << "Middle Constructor!" << std::endl;
}

class Low : Middle {
private:
  Top top;
};

int main(int, char**) {
  std::cout << "2nd Chapter 5" << std::endl;

  // Class Generation.
  Low low;

}
2.デストラク
  • 基底クラスが仮想デストラクタを持つ場合,デフォルトデストラクタとして仮想デストラクタを生成.

下記にサンプルコードを書きました.関数の仮想性が継承されることが下記のコードを実行すれば確認できますが,細かいことはまた別の項で.ここでは,親クラスのデストラクタが virtual で宣言されていれば,子クラスのデフォルトデストラクタも virtual になるということが確認できました.

下記のコードを実行してみると,インスタンスを delete するときに ChildChildBのデストラクタが呼ばれていないことがわかります.


#include <iostream>

class ParentA {
public:
  virtual ~ParentA();
};

ParentA::~ParentA() {
  std::cout << "ParentA Destructor!" << std::endl;
}

class ChildA : public ParentA {
public:
};

class ChildChildA : public ChildA {
public:
  ~ChildChildA();
};

ChildChildA::~ChildChildA() {
  std::cout << "ChildChildA Destructor!" << std::endl;  
}

class ParentB {
public:
  ~ParentB();
};

ParentB::~ParentB() {
  std::cout << "ParentB Destructor!" << std::endl;
}

class ChildB : public ParentB {
public:
};

class ChildChildB : public ChildB {
public:
  ~ChildChildB();
};

ChildChildB::~ChildChildB() {
  std::cout << "ChildChildB Destructor!" << std::endl;  
}

int main(int, char**) {
  std::cout << "2nd Chapter 5" << std::endl;
  ChildA *A = new ChildChildA();
  delete A;

  ChildB *B = new ChildChildB();
  delete B;

}
3.コピーコンストラクタ & コピー代入演算子
  • コピー元の非staticデータメンバの単純なコピー.

コピーコンストラクタとコピー代入演算子が明示的に宣言されていない場合,こちらも直感的に一番しっくりくる関数が生成されます.振る舞いとしては,そのままコピーされるだけです.この”コピー”という表現がちょっと落とし穴だと思うんですが,これはまた後の項で出てくると思うので,そのうちに...


#include <iostream>
#include <string>
#include <vector>

class A {
public:
  int a;
  float b;
  std::string name;
  std::vector<int> vec;
};

int main(int, char**)
{

  std::cout << "Copy Constructor Example" << std::endl;
  {
    A a;
    a.a = 10;
    a.b = 10.0f;
    a.name = "sample";
    a.vec.push_back(0);
    a.vec.push_back(1);
    a.vec.push_back(2);

    A aa(a);
    aa.vec.push_back(5);

    std::cout << "a.a : " << a.a << std::endl;
    std::cout << "a.b : " << a.b << std::endl;
    std::cout << "a.name : " << a.name << std::endl;

    for (std::size_t i = 0; i < a.vec.size(); i++) {
      std::cout << "elem : " << a.vec[i] << std::endl;
    }          
    std::cout << std::endl;


    std::cout << "aa.a : " << aa.a << std::endl;
    std::cout << "aa.b : " << aa.b << std::endl;
    std::cout << "aa.name : " << aa.name << std::endl;

    for (std::size_t i = 0; i < aa.vec.size(); i++) {
      std::cout << "elem : " << aa.vec[i] << std::endl;
    }
  }

  std::cout << std::endl;
  std::cout << "Operator \"=\" Example" << std::endl;
  {
    A a;
    a.a = 10;
    a.b = 10.0f;
    a.name = "sample";
    a.vec.push_back(0);
    a.vec.push_back(1);
    a.vec.push_back(2);

    A aa;
    aa = a;
    aa.vec.push_back(5);

    std::cout << "a.a : " << a.a << std::endl;
    std::cout << "a.b : " << a.b << std::endl;
    std::cout << "a.name : " << a.name << std::endl;

    for (std::size_t i = 0; i < a.vec.size(); i++) {
      std::cout << "elem : " << a.vec[i] << std::endl;
    }          
    std::cout << std::endl;


    std::cout << "aa.a : " << aa.a << std::endl;
    std::cout << "aa.b : " << aa.b << std::endl;
    std::cout << "aa.name : " << aa.name << std::endl;

    for (std::size_t i = 0; i < aa.vec.size(); i++) {
      std::cout << "elem : " << aa.vec[i] << std::endl;
    }
  }  

  return 0;
}

参考文献

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)