次の単純な小さな C++ プログラムとその出力は、スライスの問題と、それがメモリの破損につながる理由を示しています。
D、Java、C# などの言語では、参照ハンドルを介して変数にアクセスします。これは、変数に関するすべての情報が参照ハンドルに関連付けられていることを意味します。C++ では、変数に関する情報は、コンパイルが行われているときのコンパイラの状態の一部です。C++ ランタイム タイプ情報 (RTTI) を有効にすると、実行時にオブジェクト タイプを確認するメカニズムが提供されますが、スライシングの問題にはあまり役立ちません。
基本的に、C++ は速度を上げるためにセーフティ ネットを取り除きます。
C++ コンパイラには、クラスで特定のメソッド (コピー コンストラクターや代入演算子など) が提供されていない場合に、コンパイラーが独自の既定のバージョンを作成するために最善を尽くすために使用する一連の規則があります。コンパイラには、特定のメソッドが利用できない場合に、ソースステートメントの意味を表現するコードを作成する別の方法を探すために使用する規則もあります。
コンパイラが役に立ちすぎて、結果が危険になる場合があります。
この例でlevelOne
は、基本クラスとlevelTwo
派生クラスの 2 つのクラスがあります。基本クラスのオブジェクトへのポインターがオブジェクトの派生クラス部分もクリーンアップするように、仮想デストラクタを使用します。
出力では、派生クラスを基本クラスに割り当てるとスライスが発生し、デストラクタが呼び出されると、基本クラスのデストラクタのみが呼び出され、派生クラスのデストラクタは呼び出されないことがわかります。
派生クラスのデストラクタが呼び出されないという結果は、派生オブジェクトが所有するリソースが適切に解放されない可能性があることを意味します。
これが簡単なプログラムです。
#include "stdafx.h"
#include <iostream>
class levelOne
{
public:
levelOne(int i = 1) : iLevel(i) { iMyId = iId++; std::cout << " levelOne construct " << iMyId << std::endl; }
virtual ~levelOne() { std::cout << " levelOne destruct " << iMyId << " iLevel = " << iLevel << std::endl; }
int iLevel;
int iMyId;
static int iId;
};
int levelOne::iId = 1;
class levelTwo : public levelOne
{
public:
levelTwo(int i = 2) : levelOne(i) { jLevel = 2; iMyTwoId = iTwoId++; std::cout << " levelTwo construct " << iMyId << ", " << iMyTwoId << std::endl; }
virtual ~levelTwo() { std::cout << " levelTwo destruct " << iMyId << ", " << iMyTwoId << " iLevel = " << iLevel << " jLevel = " << jLevel << std::endl; }
int jLevel;
int iMyTwoId;
static int iTwoId;
};
int levelTwo::iTwoId = 101;
int _tmain(int argc, _TCHAR* argv[])
{
levelOne one;
levelTwo two;
std::cout << "Create LevelOne and assign to it a LevelTwo" << std::endl;
levelOne aa; // create a levelOne object
aa = two; // assign to the levelOne object a levelTwo object
std::cout << "Create LevelTwo and assign to it a LevelOne pointer then delete it" << std::endl;
levelOne *pOne = new levelTwo;
delete pOne;
std::cout << "Exit program." << std::endl;
return 0;
}
出力はpOne = new levelTwo;
、ID が で作成されたオブジェクトがとデストラクタの4
両方にヒットし、オブジェクトの破壊を適切に処理していることを示しています。levelTwo
levelOne
ただし、オブジェクトのデストラクタが呼び出されると、オブジェクトのデストラクタのみが実行されるように、メモリ コピーのみを行うデフォルトの代入演算子が使用されるため、levelTwo
オブジェクトtwo
へのlevelOne
オブジェクトの割り当てはスライスになります。派生クラスは解放されません。aa
aa
levelOne
次に、他の 2 つのオブジェクトは、プログラムの終了時にすべてスコープ外になるため、適切に破棄されます。このログを読むと、デストラクタが構築の逆の順序で呼び出されることを思い出してください。
levelOne construct 1
levelOne construct 2
levelTwo construct 2, 101
Create LevelOne and assign to it a LevelTwo
levelOne construct 3
Create LevelTwo and assign to it a LevelOne pointer then delete it
levelOne construct 4
levelTwo construct 4, 102
levelTwo destruct 4, 102 iLevel = 2 jLevel = 2
levelOne destruct 4 iLevel = 2
Exit program.
levelOne destruct 2 iLevel = 2
levelTwo destruct 2, 101 iLevel = 2 jLevel = 2
levelOne destruct 2 iLevel = 2
levelOne destruct 1 iLevel = 1