C++学習の虚析構関数
4992 ワード
どのような場合に虚析構関数が必要ですか?
クラスは、作成(オブジェクト)、コピー、移動、割り当て、破棄など、独自のオブジェクトが一連の操作を実行するときにどのような動作を行うかを制御する必要があります.継承システムでは、クラス(ベースクラスまたは派生クラス)がコピー制御操作を定義していない場合、コンパイラは自動的に1つを合成します.すなわち、合成されたコピー制御である.
ベースクラスのコピー制御では、継承関係による最大の影響は、ベースクラスが通常「虚析構造関数」を定義する必要があることです.継承システム内のオブジェクトを動的に割り当てます.
クラスA,B,C,Dには次のような継承関係がある(コード1):
1
2
3
4
ここで、クラスAは以下のように定義される(コード2):
1
2
3
4
5
deleteがA*itemタイプのポインタを指す場合、このポインタはAを指すか、B,C,Dのいずれかを指す可能性があります.コンパイラはdeleteの場合、A,B,C,Dのどのクラスの構造関数を実行すべきかを明らかにしなければなりません.この場合、コンパイラは動的バインドを行う必要があります(すなわち、itemがそのクラスを指していることは実行時にのみわかります).ベースクラスAで定義された構造関数が虚構造関数である場合、Aの派生クラス(B,C,D)が合成された構造関数を使用しても、自分で定義された構造関数を使用しても、それらは虚構造関数である.あなたの祖先は虚と申します.あなたに伝わっても虚と申します.あなたの息子の孫は虚と申します.この孫があなたの血縁であれ、自分で養子縁組しても、虚と申します.
例(コード3):
1
2
3
4
ベースクラスAの構造関数が虚(虚)でない場合、deleteの場合、itemがAではなくBまたは他のAの派生クラスを指す場合、未定義の動作が発生し、未定義の動作は通常BUGを引き起こす.
では、問題は、どのような場合に虚析構関数が必要なのかということです.すべてのクラスがあるはずですか?
派生クラスのオブジェクトをベースクラスのポインタで削除する場合、ベースクラスの構造関数は虚であるべきです.そうでなければ、削除効果は実現されません.
簡単に説明すると、派生クラスBのすべての属性は操作(Bp)でB自身が定義した属性、操作(Bself)だけでなく、Aから継承した属性、操作(Aself)、すなわちBp=Bself+Aselfである.
コード3のようにdeleteがBのitemを指す場合(実際にはitemのクラスタイプはA)、Aの構造関数が虚でない場合、itemのクラスタイプは実際にはAであり、その派生クラスオブジェクトを指すだけであるため、Aself部分は削除されます.しかし、Aの解析関数にはBself部分はありません.この部分は削除できません.これはいわゆるメモリ漏れです.Aの解析関数だけが虚しく,削除できるのはAselfだけでなくBself,すなわちBpがすべて削除されている.これこそ正しい.
また、すべてのクラスが構造関数を虚として定義する必要はありません.コンパイラはコンパイル時にクラスに虚関数テーブルを追加するため、虚関数ポインタを格納し、すべて虚と定義すると、クラスの格納スペースが増加します.もったいない!ベースクラスにしなくても、虚にする必要はありません!派生クラスのオブジェクトをベースクラスのポインタで操作する必要がない場合、ベースクラスの構造関数は虚であるべきではありません.
ここで文章コードを借ります:仮想構造関数はいつ使用しますか?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
正常な場合は、次のように出力します.
1
2
クラスClxBaseの構造関数を非虚(前のvirtualを除く)として定義すると、次のように出力されます.
1
ClxDerivedの構造関数を呼び出すことはありませんよ~~~
同様に、仮想構造関数はいつ使用しますか? において、このような問題が提起されている.
なぜ虚解析関数のないクラスを継承するのは危険ですか?
この問題は実は上ですでに説明したことがあって、削除しきれないことを招きます!メモリリークの問題.継承を共有してベースクラスから継承された関連クラスを作成すると、新しいクラスオブジェクトへのポインタと参照は実際には起源のオブジェクトを指します.構造関数は虚関数ではないので、deleteのようなクラスでは、C++は構造関数チェーンを呼び出すことはありません.
為知ノート(Wiz)から
クラスは、作成(オブジェクト)、コピー、移動、割り当て、破棄など、独自のオブジェクトが一連の操作を実行するときにどのような動作を行うかを制御する必要があります.継承システムでは、クラス(ベースクラスまたは派生クラス)がコピー制御操作を定義していない場合、コンパイラは自動的に1つを合成します.すなわち、合成されたコピー制御である.
ベースクラスのコピー制御では、継承関係による最大の影響は、ベースクラスが通常「虚析構造関数」を定義する必要があることです.継承システム内のオブジェクトを動的に割り当てます.
クラスA,B,C,Dには次のような継承関係がある(コード1):
1
2
3
4
class
A;
class
B:
public
A;
class
C:
public
B;
class
D:
public
C;
ここで、クラスAは以下のように定義される(コード2):
1
2
3
4
5
class
A {
public
:
//
virtual ~A()=
default
;
//
};
deleteがA*itemタイプのポインタを指す場合、このポインタはAを指すか、B,C,Dのいずれかを指す可能性があります.コンパイラはdeleteの場合、A,B,C,Dのどのクラスの構造関数を実行すべきかを明らかにしなければなりません.この場合、コンパイラは動的バインドを行う必要があります(すなわち、itemがそのクラスを指していることは実行時にのみわかります).ベースクラスAで定義された構造関数が虚構造関数である場合、Aの派生クラス(B,C,D)が合成された構造関数を使用しても、自分で定義された構造関数を使用しても、それらは虚構造関数である.あなたの祖先は虚と申します.あなたに伝わっても虚と申します.あなたの息子の孫は虚と申します.この孫があなたの血縁であれ、自分で養子縁組しても、虚と申します.
例(コード3):
1
2
3
4
A *item =
new
A;
// item A, ( )
delete
item;
// A ( , )
item =
new
B;
// A, B( ,item !)
delete
item;
// B ( , , )
ベースクラスAの構造関数が虚(虚)でない場合、deleteの場合、itemがAではなくBまたは他のAの派生クラスを指す場合、未定義の動作が発生し、未定義の動作は通常BUGを引き起こす.
では、問題は、どのような場合に虚析構関数が必要なのかということです.すべてのクラスがあるはずですか?
派生クラスのオブジェクトをベースクラスのポインタで削除する場合、ベースクラスの構造関数は虚であるべきです.そうでなければ、削除効果は実現されません.
簡単に説明すると、派生クラスBのすべての属性は操作(Bp)でB自身が定義した属性、操作(Bself)だけでなく、Aから継承した属性、操作(Aself)、すなわちBp=Bself+Aselfである.
コード3のようにdeleteがBのitemを指す場合(実際にはitemのクラスタイプはA)、Aの構造関数が虚でない場合、itemのクラスタイプは実際にはAであり、その派生クラスオブジェクトを指すだけであるため、Aself部分は削除されます.しかし、Aの解析関数にはBself部分はありません.この部分は削除できません.これはいわゆるメモリ漏れです.Aの解析関数だけが虚しく,削除できるのはAselfだけでなくBself,すなわちBpがすべて削除されている.これこそ正しい.
また、すべてのクラスが構造関数を虚として定義する必要はありません.コンパイラはコンパイル時にクラスに虚関数テーブルを追加するため、虚関数ポインタを格納し、すべて虚と定義すると、クラスの格納スペースが増加します.もったいない!ベースクラスにしなくても、虚にする必要はありません!派生クラスのオブジェクトをベースクラスのポインタで操作する必要がない場合、ベースクラスの構造関数は虚であるべきではありません.
ここで文章コードを借ります:仮想構造関数はいつ使用しますか?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class
ClxBase
{
public
:
ClxBase() {};
virtual ~ClxBase() {};
virtual
void
DoSomething() { cout <<
"Do something in class ClxBase!"
<< endl; };
};
class
ClxDerived :
public
ClxBase
{
public
:
ClxDerived() {};
~ClxDerived() { cout <<
"Output from the destructor of class ClxDerived!"
<< endl; };
void
DoSomething() { cout <<
"Do something in class ClxDerived!"
<< endl; };
};
ClxBase *pTest =
new
ClxDerived;
pTest->DoSomething();
delete
pTest;
正常な場合は、次のように出力します.
1
2
Do something
in
class
ClxDerived!
Output from the destructor of
class
ClxDerived!
クラスClxBaseの構造関数を非虚(前のvirtualを除く)として定義すると、次のように出力されます.
1
Do something
in
class
ClxDerived!
ClxDerivedの構造関数を呼び出すことはありませんよ~~~
同様に、仮想構造関数はいつ使用しますか? において、このような問題が提起されている.
なぜ虚解析関数のないクラスを継承するのは危険ですか?
この問題は実は上ですでに説明したことがあって、削除しきれないことを招きます!メモリリークの問題.継承を共有してベースクラスから継承された関連クラスを作成すると、新しいクラスオブジェクトへのポインタと参照は実際には起源のオブジェクトを指します.構造関数は虚関数ではないので、deleteのようなクラスでは、C++は構造関数チェーンを呼び出すことはありません.
為知ノート(Wiz)から