class A{ A(); ~A(); };
int main(){ A samp; }
int main(){ A* p =nullptr; p = new A; delete p; }
逆に言うと、newしてメモリを確保すると、明示的にdeleteしなければデストラクタは呼ばれない
(そもそも確保したオブジェクトのメモリが消えてくれない)
class A{ A(); ~A(); foo(){ cout<< "bar\n"; } }; class B :public class A { B(); ~B(); }
継承関係のあるクラスでは、基本的に
の順に呼び出されます。
動的にメモリを確保したときも同様です。
という事で、ちゃんとdeleteしなければデストラクタが呼ばれないのも一緒です。
派生クラスのインスタンスは、ベースクラスのポインタと関連付けて(ベースクラスのポインタに代入できる)利用することができる。
ただし、その時は派生クラス内のベースクラスのメンバにしかアクセスできない。
さらにその時に、派生クラスで、ベースクラスのメンバ関数をオーバーライドしていると、
(ベースクラスのポインタからアクセスしているにもかかわらず)派生クラスのメンバ関数が呼び出される(意味不明)。
さらに、さらにその時のコンストラクタとデストラクタの呼び出し順を確認してみる。
class A { public: A(){cout << "const A" << "\n";} ~A(){cout << "dest A" << "\n";} void foo(){ cout<< "bar\n";} }; class B :public A { public: B(){cout << "const B" << "\n";} ~B(){cout << "dest B" << "\n";} void foo() { cout<< "barbar\n";} }; int main() { A* p1 = nullptr; p1 = new B; p1->foo(); delete p1; }
現在ベースクラスと派生クラスのfoo()関数は隠蔽関係にある。
A::foo() は B::foo() によって隠蔽されている。
(BのインスタンスからはAの同名関数は隠されていて見えない)
B samp; samp.foo(); //barbar が表示される
しかし、以下のソースコードの様にベースのポインタ経由でアクセスすると。。。
A* p1 = nullptr; p1 = new B; p1->foo(); delete p1;
イメージとしては下図のようになる。
A* p = new B;
Bのインスタンス
A* p +--------------+
+-----------------+--> |0x1111 |
| | +--------------+
| &A ::A()<----|----|0x1112 A::A() |
| & ::~A()<---|----|0x1113 ::~A()|
| & ::foo <---|----|0x1114 ::foo |
+-----------------+ +--------------+
|0x1115 B:: B()|
|0x1116 ::~B()|
|0x1117 ::foo |
+--------------+
図のような関係になるので、pからはベース側のfoo()にしかアクセスできない。
したがって、
A* p1 = nullptr; p1 = new B; p1->foo(); delete p1;
の結果は、
(説明のための書き方なのでプログラムではこう書けないよ)
のようになる。
先ほど隠蔽関係だった A::foo() と B::foo() を今度は、AのfooをBのfooで上書きしB::fooの機能を実行するように書き換えてみます。
ベース側の関数を仮想関数宣言(virtual)して、派生側でオーバライドすればいいんだったね。(override)
class A { public: A(){cout << "const A" << "\n";} ~A(){cout << "dest A" << "\n";} virtual void foo(){ cout<< "bar\n";} }; class B :public A { public: B(){cout << "const B" << "\n";} ~B(){cout << "dest B" << "\n";} void foo() override { cout<< "barbar\n";} }; int main() { A* p1 = nullptr; p1 = new B; p1->foo(); delete p1; }
仮想関数とオーバーライドの効果により、ポインタのつながりの図がちょっと複雑になります。
しかしながら、A* pからは、ベースクラスであるAのメンバにしかアクセスできていないのは実は変わりません
A* p = new B;
Bのインスタンス
A* p +---------------------+
+-----------------+--> |0x1111 |
| | +---------------------+
| &A ::A()<----|----|0x1112 A::A() | 仮想関数テーブル
| & ::~A()<---|----|0x1113 ::~A() | +---+-------------------+--------+
| & ::foo <---|----|0x1114 virtual foo() +---+ | x | A::foo() |0x2111 |
+-----------------+ +---------------------+ | +---+-------------------+--------+
|0x1115 B:: B() | +---+ O | B::foo() override |0x21112 |
|0x1116 ::~B() | +---+-------------------+--------+
+---------------------+
今度は、B内のAにある仮想関数が、B::foo()のアドレスに接続されているのでA::foo()にアクセスすると
強制的に上書きされた方の関数(=B::foo())にアクセスするようになります。
したがって結果は “barbar”と表示されます。
仮想関数の仕組み的には、仮想関数をオーバライドして関数がすげ変わったんでオッケー!と思ったかもしれないですが、よく見ると
Bのコンストラクタが呼ばれていません。。。そりゃそうですね、上のポインタのつながりの図を見ると、Aにしかつながってないもん。
という事で、Bのデストラクタ、Aのデストラクタの順に呼ばれるようにするにはどうしたらいいか考えます。
そうですね、Aのデストラクタを仮想関数にすればよいです。(デストラクタの名前違うからオーバライドではないんだよね)
class A { public: A(){cout << "const A" << "\n";} virtual ~A(){cout << "dest A" << "\n";} virtual void foo(){ cout<< "bar\n";} }; class B :public A { public: B(){cout << "const B" << "\n";} ~B(){cout << "dest B" << "\n";} void foo() override { cout<< "barbar\n";} }; int main() { A* p1 = nullptr; p1 = new B; p1->foo(); delete p1; }
結果は以下のようになり、ちゃんとBのデストラクタ ⇒ Aのデストラクタ の順に呼ばれるようになります。
ポインタはアドレスを入れるための、ハコです。
アドレスの値はシステムで固定の長さの整数ですが、ポインタには型があります。
ポインタの型はその先に何が格納されるかを書きます。
この辺は、むかーし昔にやったので、省きます。
さて、ポインタの先のメモリにクラスがあるとどうなるでしょうか?
クラスにはメンバ変数と、メンバ関数などのメンバがあるので、以下のような図で考えてみます。
A* p = nullptr; +-----------------+--> nullptr ←インスタンスの先頭アドレス用差込口 | | | &A ::A()<----| ←A()接続用差込口 | & ::~A()<---| ←~A()接続用差込口 | & ::foo <---| ←foo()接続用差込口 +-----------------+
これにインスタンスを接続(メモリを確保して、アドレスを代入)してみます。
まずインスタンスを生成します。newにより実体が確保されその先頭アドレス(0x1111)が返されます。
A* p = nullptr;
p = new A;
アドレス0x1111にAのインスタンスが生成される。
+--------------+
|0x1111 |
+--------------+
|0x1112 A::A() |
|0x1113 ::~A()|
|0x1114 ::foo |
+--------------+
図のように、インスタンスの先頭アドレスの他に、その中にパックされているメンバもそれぞれアドレスを持っています。
(アドレスの値などは説明のためのでたらめな値です)
次に p = new A; の文でA* 型のポインタ変数pに、newで取得したインスタンスのアドレス(=0x1111)を代入します。
A* p +--------------+ +-----------------+--> |0x1111 | | | +--------------+ | &A ::A()<----|----|0x1112 A::A() | | & ::~A()<---|----|0x1113 ::~A()| | & ::foo <---|----|0x1114 ::foo | +-----------------+ +--------------+
その結果、
のように、ポインタ変数pからインスタンスの各メンバにアクセスできるようになります。
C++では、派生クラスのインスタンスは必ず内部にベースクラスを含んでいます。
このときに、先ほどと同じようにclass Bのインスタンスを動的に作った時を考えてみます。
class Bで追加されたメンバはコンストラクタとデストラクタのみなので、以下のようになります。
B* p = nullptr;
p = new B;
アドレス0x1111にBのインスタンスが生成される。
+--------------+
|0x1111 |
+--------------+
|class A |
|0x1112 A:: A()|
|0x1113 ::~A()|
|0x1114 ::foo |
+--------------+
|0x1115 B:: B()|
|0x1116 ::~B()|
+--------------+
このときに、生成されたclass Bのインスタンスは丸々class Aを含んでいます。
(逆に言うとB内には必ずAが存在する)
ので、class AのポインタにBのA部分を接続することができます。
A* p = new B;
A* p +--------------+
+-----------------+--> |0x1111 |
| | +--------------+
| &A ::A()<----|----|0x1112 A::A() |
| & ::~A()<---|----|0x1113 ::~A()|
| & ::foo <---|----|0x1114 ::foo |
+-----------------+ +--------------+
|0x1115 B:: B()|
|0x1116 ::~B()|
+--------------+
これが、派生クラスにベースクラスのポインタからアクセスする仕組みです。
ただし、図を見ると当たり前そうですが、接続されているのはB内のAのメンバのみです。
なので、pからはBの持つAのメンバにしかアクセスできません。