概要
そこのc++超初心者の方、以下のデストラクタの挙動がわかってないとメモリリーク起こして詰みます。(自分に対するブーメラン)
これは、https://stackoverflow.com/questions/2403020/why-doesnt-the-c-default-destructor-destroy-my-objects/2403026
を読んだときの備忘録です。(口調がちょっと刺々しいけどしっかり答えてくれるstockoverflowの住人に感謝です。)
登場人物
- クラスM1
- クラスM2
- クラスN
これわからないとヤヴァイ
クラスM1とクラスM2を次のように作ります。
このコードを見てみてください。
- クラスM1とクラスNがあります。
- クラスM1はメンバとしてクラスNの実体を持っています。
# include <stdio.h>
class N{
public:
int n_member1;
N(){
n_member1 = 1;
};
~N(){printf("I am a destructor of N\n");};
};
class M
{
public:
N n;
~M (){printf("I am a destructor of M\n");}
M()
{
n = N();
}
};
次にこのコードを見てみてください。
- クラスM2とクラスNがあります。
- クラスM2はメンバとしてクラスNのポインタを持っています。
ちな、new 演算子をわからない人はいないと思いますが、(私は最近知りました)
new 演算子はヒープ領域にオブジェクトを作り、そのオブジェクトへのポインタを返します。
この場合、M2のメンバ変数nはヒープ領域にnew演算子を通して作られます。
class M2
{
public:
N* n;
~M2 (){printf("I am a destructor of M2\n");}
M2()
{
n = new N();
}
};
クラスMの消失
int main(){
M m;
}
として、クラスMのオブジェクトmを作ります。
これらはnew演算子で作られたものではないので、main関数のスコープを抜けると、デフォルトでデストラクタが呼ばれ、メモリから消えます。
実際に走らせると、
I am a destructor of N
I am a destructor of M
I am a destructor of N
となります。これをよく観察すると、
一行目のI am a destructor of N
は、クラスMのコンストラクタ内で、n = N();
とした後に、スコープから抜けてこのn
がデストラクトされたときのものです。
二行目のI am a destructor of M
はmain関数のスコープから抜けて、クラスMのオブジェクトmがデストラクトされたものです。
mはクラスNのオブジェクトnの実体をメンバとして持っていたので、mが消えたときにnの実体も消えます。
したがって、三行目のI am a destructor of N
は、mが消えたときにnも消えるので呼ばれたのです。
クラスM2の消失
では、クラスM2はどうでしょうか。
int main(){
M2 m2;
}
とします。すると、
I am a destructor of M2
と出ます。もちろん、I am a destructor of M2
は、main関数のスコープが終わるときに、m2がデストラクトされるから表示されています。
しかし、I am a destructor of N
とはなっていませんよね。
これは、m2がクラスnの実体ではなく、ポインタを所持しているので、m2がデストラクトされるときに同じくデストラクトされるのは、nそのものではなくて、nへのポインタだからです。
すなわち、new演算子で作られたクラスNのオブジェクトnは消えておらず、nへのポインタだけが消えたということです。
これを繰り返すと、クラスMのオブジェクトをコンストラクトするたびにクラスNのオブジェクトがヒープ領域に作られ、クラスMのオブジェクトがスコープを越えて消滅した際にも、クラスNのオブジェクトは宙ぶらりんになってヒープ領域に残ることになります。
考えてみれば当たり前ですが、これを理解しとかないといろいろ不都合なことになりますね。
まとめ
おそらく、このようなことが置きないように、ポインタをメンバとしてもたせるクラスを作るときは、
そのメンバがポイントする先が増殖しない。 ような際に限るべき。
言い換えると、
クラスのオブジェクト(クラスM)と、ポイントする先(クラスN)が多:少数の関係になっているべき。 でしょう。
そうでなければ、クラスオブジェクト(クラスM)は、ポインタでなく実体をメンバとして持っておいたほうが安全なような気がします。
クラスオブジェクトが消える際に、参照しているメンバも消えるようにしておくように、スマートポインタというものがあるようですが、これについてはこれから勉強してみます。
おわり。