LoginSignup
3
0

More than 3 years have passed since last update.

コンストラクタやデストラクタ内では決して仮想関数を呼び出さないようにしよう - Effective C++ 第3版 9項-

Posted at

 はじめに

Effective C++ 第3版の9項 44ページから勉強していきます。
今回は、「コンストラクタやデストラクタ内では決して仮想関数を呼び出さないようにしよう」についです。

Effective C++ 第3版 - 9項 コンストラクタやデストラクタ内では決して仮想関数を呼び出さないようにしよう -

前置き

今回は、コンストラクタやデストラクタ内で仮想関数を呼び出してはいけない理由について勉強していきます。

今回の勉強内容

デストラクタでの例外

今回は、Baseという基底クラスとBaseを継承したChildというクラスを扱います。
また、Baseは、printLogという仮想関数を保持しています。
以下に、2つのクラスを示します。

class Base {
 public:
  Base() { printLog(); };
  ~Base(){};
  virtual void printLog() { std::cout << "Base :  printLog()" << std::endl; };
};

class Child : public Base {
 public:
  Child(){};
  ~Child(){};
  void printLog() { std::cout << "Child :  printLog()" << std::endl; }
};

ここで、次のようにChildクラスのオブジェクトの生成したときの処理について見ていきます。

Child ch;
実行結果
Base :  printLog()

これによって、Childのコンストラクタが呼ばれます。
しかし、その前に基底クラスであるBaseのコンストラクタが呼び出されます。
派生クラスのオブジェクトを生成するときには、その基底クラスの部分が先に生成されることになっているからです。

今回、作成したBaseのコンストラクタでは、仮想関数の printLog() を呼び出しています。
ここで呼ばれるprintLog()は、実行結果からも分かるように、ChildのprintLog()ではなく、BaseのprintLog()になります。
生成されているオブジェクトは、Childであるにもかかわらず、BaseのprintLog()が呼ばれるようになります。

これは、基底クラスのコンストラクタが呼び出されている間は、仮想関数は決して派生クラスのものにならないからです。
また、その間は、オブジェクトも基底クラスのものとして扱われます。

オブジェクトの破棄についても同様のことが言えます。
「派生クラス」のデストラクタが実行されると、まず、「派生クラス」の部分が破棄され、その次に「基底クラス」のデストラクタが実行され、「基底クラス」の部分が破棄されます。
そのため、基底クラスのデストラクタ内で仮想関数を呼んだ場合、呼ばれるのは、基底クラスの仮想関数が呼ばれます。

このような理由から、コンストラクタやデストラクタ内では、仮想関数を呼び出さないようにする必要があります。

サンプルコード

以下に、勉強で使用したコードを示します。

#include <iostream>

class Base {
 public:
  Base() { printLog(); };
  ~Base(){};
  virtual void printLog() { std::cout << "Base :  printLog()" << std::endl; };
  /*
 private:
  void         init() { printLog(); }*/
};

class Child : public Base {
 public:
  Child(){};
  ~Child(){};
  void printLog() { std::cout << "Child :  printLog()" << std::endl; }
};

int main() {
  std::cout << "" << std::endl;
  Child ch;
  ch.printLog();
}

実行結果

実行結果

Base :  printLog()
Child :  printLog()

まとめ

今回は、コンストラクタやデストラクタ内で仮想関数を呼んではいけない理由について学びました。

覚えておくこと

  • オブジェクト生成や破棄の間に仮想関数を呼び出してはいけない。
  • そのような呼び出しで実行されるのは、オブジェクトの生成や破棄の途中のオブジェクトの型になる。

メモ

Baseのコンストラクタ内で仮想関数であるprintLog()を呼ばずに、Childのコンストラクタ内で printLog()を呼んだ場合
→ 普通にChildのprintLog()が呼ばれた。

参考文献

[1] https://www.amazon.co.jp/gp/product/4621066099/ref=dbs_a_def_rwt_hsch_vapi_taft_p1_i0

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0