2016-04-16 9 views
0

なぜ次のコードがクラッシュしないのか、それがどう処理されないのか説明してください。オブジェクトポインタでデストラクタを呼び出す

class Base 
{ 
public: 
    Base() {cout << "Base constr" << endl;} 
    virtual void func() {cout << "Base func()" << endl;} 
    virtual ~Base() {cout << "Base destr" << endl;} 
}; 

class Layer1 : public Base 
{ 
public: 
    Layer1() {cout << "Layer1 constr" << endl;} 
    virtual void func() {cout << "Layer1 func()" << endl;} 
    virtual ~Layer1() {cout << "Layer1 destr" << endl;} 
}; 

class Layer2 : public Layer1 
{ 
public: 
    Layer2() {cout << "Layer2 constr" << endl;} 
    virtual void func() {cout << "Layer2 func()" << endl;} 
    ~Layer2() {cout << "Layer2 destr" << endl;} 
}; 

int main(int argc, char** argv) 
{ 
    Layer2 * l2ptr = (Layer2 *) new Base; 
    l2ptr->func(); 
    delete l2ptr; 

    return 0; 
} 

出力:

Base constr 
Base func() 
Base destr 

私はl2ptrが呼び出された時点で削除を意味します。最初の外観からは、Layer2デストラクタを呼び出す必要がありますが、Layer2オブジェクトは作成されていないようです。また、Layer2デストラクタはvirtualではありません。私がそれを仮想化すると、出力は同じになります。どうして?

そして、次の質問:それは子クラスポインタによって親クラスオブジェクトにアクセスする際にどのような問題と考慮事項がありますか?

EDIT: 私はこのようなこの

class Layer2 : public Layer1 
{ 
public: 
    Layer2() {cout << "Layer2 constr" << endl;} 
    virtual void func() {cout << "Layer2 func()" << endl;} 
    void func2() {cout << "Layer2 func2()" << endl;} 
    virtual ~Layer2() {cout << "Layer2 destr" << endl;} 
}; 

そしてmain()Layer2クラスを変更した場合:

int main(int argc, char** argv) 
{ 
    Layer2 * l2ptr = (Layer2 *) new Base; 
    l2ptr->func(); 
    l2ptr->func2(); 
    delete l2ptr; 

    return 0; 
} 

それはまだ動作し、出力は次のとおりです。

Base constr 
Base func() 
Layer2 func2() 
Base destr 

再び、 どうして?

+4

コードに未定義の動作があります。それは何かが起こりうることを意味し、あなたは何かが起こっていることを観察しています。 –

+0

あなたは '(Layer2 *)new Base'の少し後にUBを持っています... – Jarod42

+0

http://blog.llvm.org/2016/04/undefined-behavior-is-magic.html – JVApen

答えて

0

Layer2へのポインタを介してBaseオブジェクトにアクセスしたときのように、コードには未定義の動作があります。

未定義の動作には、動作するように見えることが含まれます(実際には他のもの)。この場合、オブジェクトのレイアウトは十分に似ているので、何も悪いことは間違っていません。 vtableポインタは同じ場所にあります。オブジェクトがBaseで、Base vtableへのポインタが含まれているため、仮想関数の呼び出しは正しくBaseバージョンになります。非仮想関数は、オブジェクトがコンパイラに指示した型に移動します。

(実装はvtableのを使用する必要はありませんが、彼らは通常は行う。)

をしかし、それはすべて、まだ未定義の動作です。これをしないでください、そして、間違いなくそれに頼らないでください。

+0

ありがとう、私はそれを得た。私は本当にUBと一緒に作りたいとは考えていませんが、私は興味があります。なぜですか?もう2つの質問:(1)しかし、なぜレイヤー2とレイヤー1のデストラクタが削除l2ptrで呼び出されないのですか? (2)削除と一般的にC++がデストラクタを呼び出すときにどのように動作するか(btw、私はdeleteがヒープmamoryを解放するlib関数だと知っています)? –

+0

デストラクタはすべて仮想です。 'delete l2ptr'を実行すると、vtableを探し、' Base'デストラクタを見つけて呼び出します。あなたが本当の 'Layer2'オブジェクトを持っていたなら、vtableはそのデストラクタを指しています。デストラクタはそのベースデストラクタを呼び出します。 –

+0

「デストラクタはすべて仮想です」とはどういう意味ですか?常に?私がレイヤー2を仮想として宣言すると、それは正しいです:デストラクタは、vtableディスパッチを解決することによって呼び出されます。しかし、私はそれを仮想として宣言しないと、静的に解決できます。しかし、この場合は動作しません。 –

4

あなたのコードには未定義の動作があるため、何かを行う理由についてのほとんどの質問は意味がなく、答えられません。何か特別なことをする限り、それは基本的には運がいいだけです。ここで

int main(int argc, char** argv) 
{ 
    Base *bptr = new Layer2; 
    bptr->func();  

    Layer2 *l2ptr = dynamic_cast<Layer2 *>(bptr); 

    if (l2ptr) 
     l2ptr->func2(); 

    delete bptr; 
} 

は、我々がpointer to Baseを使用します。

あなたはこの順にmain何かを持っていると思われる、基本的に後方あなたはおそらく(またはあなたがどのような場合には意図されているはず何の)意図したもののものを持っていますその逆ではなくタイプLayer2のオブジェクトを参照してください。 Layer2はBaseから派生しているため、これは許可されており、その動作は意味があります。

これは、なぜそれが何をするのかの詳細について話すことができます。

Layer2virtualとマークされていないデストラクタは、基本的に無関係である:[class.virtual]/2条、

C++標準:それは基本クラスでvirtualをマークしていますので、全ての派生クラスでvirtualまま

仮想メンバ関数vfが、クラスBaseと、Baseから直接または間接的に派生したクラスDerivedで宣言されている場合、同じ名前のメンバ関数vf parameter-type-list(8.3.5) cv-qualification、および、Base :: vfが宣言されているref-qualifier(または同じものが存在しない)の場合、Deri ved :: vfもvirtual(それが宣言されているかどうかにかかわらず)であり、が上書きされます。 Base :: vf。

C++標準、[class.virtual]/6§:

デストラクタが継承されていない場合でも、派生クラスのデストラクタは、基本クラスのデストラクタが仮想宣言オーバーライド。

+0

その逆も私のポストにあるように意図されていました。しかし、あなたの前提と提案されたコードは高く評価されます。そして、ベースからの仮想的な継承について:私はその情報をC++リファレンスで見つけることができますか? –

+0

あなたに大きな感謝。尊敬。 –

0

私が間違っていても、Layer2またはLayer1のオブジェクトは作成しないでください。 Layer2型へのポインタに(Cスタイルではなく、C++ではなく)キャストしたBaseオブジェクトだけです。つまり、func2はポインタが指すオブジェクトでは使用できませんが、コンパイラはポインタの静的型のみをチェックするため、ポインタを受け入れます。

関連する問題