追記
追記をここに示していきます。
コメントにて、間違いを教えていただきました。(2022/10/19)→ 追記1
はじめに
私がC++で継承について勉強していた時に、ある人に「親クラスのデストラクタには仮想関数にしないといけないらしいよ」と言われたので、それはなぜなのか調べました。
検証
まず、仮想関数にではないデストラクタで結果を見てみました。
#include <iostream>
#include "Parent.h"
#include "Child.h"
int main()
{
std::cout << "メインが実行されました";;
// Parentのインスタンス
Parent* abc;
abc = new Child;
delete abc;
}
class Parent
{
protected:
public:
Parent() { std::cout << "Parentの「コンストラクタ」が実行されました"; }
~Parent() { std::cout << "Parentの「デストラクタ」が実行されました"; }
};
class Child : public Parent
{
private:
public:
Child() { std::cout << "Childの「コンストラクタ」が実行されました"; }
~Child() { std::cout << "Childの「デストラクタ」が実行されました"; }
};
実行してみた結果...
メインが実行されました
Parentの「コンストラクタ」が実行されました
Childの「コンストラクタ」が実行されました
Parentの「デストラクタ」が実行されました
となりました!
ここで、一つ呼ばれていない処理があります。
Childのデストラクタですね。
では次に、Parentのデストラクタを仮想関数にしてみます。
class Parent
{
protected:
public:
Parent() { std::cout << "Parentの「コンストラクタ」が実行されました"; }
virtual ~Parent() { std::cout << "Parentの「デストラクタ」が実行されました"; }
};
実行結果は...
メインが実行されました
Parentの「コンストラクタ」が実行されました
Childの「コンストラクタ」が実行されました
Childの「デストラクタ」が実行されました
Parentの「デストラクタ」が実行されました
呼ばれました!
仮想関数にしなければいけないのは子クラスのデストラクタを実行させるためだったのでしょう。
おわりに
今回は親クラスのデストラクタを仮想関数にしなければいけない理由について探っていきました。
このページで紹介しているのはあくまで、一学生である私が試したものであるため、本来の理由は違っているかもしれませんのでお気を付けください。
参考資料
未定義について
https://trap.jp/post/538/
追記1
Childクラスのデストラクタが呼ばれていないのは、宣言の時にParentクラスのポインタで宣言しているため、デストラクタ実行の際にParentのデストラクタが参照されます。それが原因で、そのデストラクタが仮想関数でないと、Childのデストラクタを参照できないとのことです。
理屈的には、いつもの仮想関数の使い方と同じですが、デストラクタや、コンストラクタの呼ばれ方が厄介なだけに難しくなっています。
追記2
未定義動作についてですが、C++では人によって手元の結果が変化したりする動作があります。
その場合、動作の種類として三つの種類が定義されていて、そのうちの未定義動作というのは企画で動作が定められていないようです。
実行結果が毎回変わったりする場合は未定義動作を疑うべきだそうです。