さて、次は
3、コンストラクタとデストラクタについて
コンストラクタとデストラクタについて、考えていってみまっしょい。
通常クラスの生成は、
class A
{
public:
A(){};
~A(){};
};
int main()
{
{
A a;
//生成されてからの寿命はスコープ間に限定
//確保メモリはスタックに詰まれる
}
{
A *pa = new A();
delete pa;
//寿命は開放するまで
//動的に確保する
}
}
下記のnew/delete表記法だと、コンストラクタとデストラクタの実行が何時起こるかは
new/deleteの実行タイミングですが、
ではでは、上記の記法だと、どうなるでしょう?
{
A a;
A::A()
}
A::~A()
って、こんな感じでした。コンパイラは、スコープを抜ける前にデストラクタ関数呼び出しを
埋め込むって感じですかね。これだとCでの実装も簡単そうですね。
CwithClassは、こんな感じでクラスを導入していったんでしょか。
ちなみに、
{
A a;
B b;
}
の場合は、
{
A::A();
B::B();
}
B::~B();
A::~A();
でした。
この辺のコンストラクタとデストラクタの順序関係は、クラスAの参照をクラスBが使っていた場合の
ためでしょうね。こんなのを見るとDXの取得した順の逆に開放を思い出しますね。
多分、似たような問題があるんでしょか?
4、仮想関数のオーバーライドについて
で、仮想関数のオーバーライドについてです。
本題ですが、引っ張っても問題なのでちゃっちゃっとやりまっしょい。
さて始めに、
class Base
{
public:
Base(){cout<<"start Base"<<endl;};
~Base(){cout<<"end Base"<<endl;}
};
class Derivative : public Base
{
public:
Derivative(){cout<<"start Deriva"<<endl;};
~Derivative(){cout<<"end Deriva"<<endl;};
};
int main()
{
{
Derivative *pd = new Derivative();
delete pd;
}
cout<<endl;
{
Derivative *pd = new Derivative();
Base *pb = static_cast<Base*>(pd);
delete pb;
}
}
//実行結果
start Base
start Deriva
end Deriva
end Base
start Base
start Deriva
end Base
上記のように派生クラスのポインタを基本クラスのポインタにキャストした後、
開放すると、派生クラスのデストラクタが呼び出されない問題があります。
結構有名な話で、これの解決策として、「デストラクタをVirtualにする」
っていうのが、常識です。
これ自体に特に問題も異論も無いのですが、私がこれを聞いた時は、なぜVirtualにすれば
問題が解決するのかずっと疑問でした。(Virtualの動作原理を良く知らなかったので)ので、調べてみたわけです。
ところで、上記の問題はdeleteに問題があり、基本クラスにキャストされて渡された元派生クラスのポインタを
基本クラスとして、deleteするので、基本クラスのデストラクタしか呼ばれません。
ですので、下記のように、キャストはしても
{
Derivative pd;
Base *pb = static_cast<Base*>(&pd);
}
deleteが、なければ特に問題も無いわけです。
静的に確保したメモリ領域なので、確保した時点で開放も決まっていますが、
動的にメモリを確保し、キャストなどをした場合上記のように問題となります。
さてさて、Virtualについて考えていきます。
Virtual関数を含まない時は、
class B
{
private:
int b1;
int b2;
}
class D : public B
{
private:
int d1;
int d2;
}
int main()
{
D* pd = new D();
B* pb = static_cast<B>(pd);
cout << "pd" << ' ' << (pd) << endl;
cout << "pb" << ' ' << (pb) << endl;
cout << "b1" << ' ' << &(pd->b1) << endl;
cout << "b2" << ' ' << &(pd->b2) << endl;
cout << "d1" << ' ' << &(pd->d1) << endl;
cout << "d2" << ' ' << &(pd->d2) << endl;
}
//出力
pd 00343D98
pb 00343D98
b1 00343D98
b2 00343D9C
d1 00343DA0
d2 00343DA4
メモリ配置はこんな感じでしょう。
address value
0x0000 *pb,*pd->B::b1
0x0004 B::b2
0x0008 D::d1
0x000C D::d2
さて、次にVritual関数を含む時です。
class B
{
private:
int b1;
int b2;
virtual void fb1();
virtual void fb2();
}
void B::fb1()
{cout << "fb1" << endl;}
void B::fb2()
{cout << "fb2" << endl;}
class D : public B
{
private:
int d1;
int d2;
}
int main()
{
D* pd = new D();
B* pb = static_cast<B>(pd);
cout << "pd" << ' ' << (pd) << endl;
cout << "pb" << ' ' << (pb) << endl;
cout << "b1" << ' ' << &(pd->b1) << endl;
cout << "b2" << ' ' << &(pd->b2) << endl;
cout << "d1" << ' ' << &(pd->d1) << endl;
cout << "d2" << ' ' << &(pd->d2) << endl;
}
address value
0x0000 *pb,*pd->B::b1
0x0004 B::b2
0x0008 D::d1
0x000C D::d2