いつ仮想デストラクタを使用するのですか?


1486

私はほとんどのオブジェクト指向理論をしっかりと理解していますが、私を混乱させるのは仮想デストラクタです。

デストラクタは、チェーン内のすべてのオブジェクトに関係なく、常に呼び出されると思いました。

いつ仮想化するつもりですか?なぜですか?


6
これを参照してください:Virtual Destructor
Naveen

146
ダウンしているすべてのデストラクタは、何があっても呼び出されます。 virtual中央ではなく上部から始まることを確認します。
Mooing Duck 2013


@MooingDuckは、誤解を招くコメントです。
Euri Pinhollow 2017

1
@FranklinYuコメントで問題が見られなくなったので、あなたが質問したのは良いことです(コメントで回答を試みることを除く)。
Euri Pinhollow 2017年

回答:


1572

仮想デストラクタは、基本クラスへのポインタを介して派生クラスのインスタンスを削除する可能性がある場合に役立ちます。

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

ここで、Baseのデストラクタがであると宣言していないことに気付くでしょうvirtual。それでは、次のスニペットを見てみましょう。

Base *b = new Derived();
// use b
delete b; // Here's the problem!

ベースのデストラクタではないので、virtualbあるBase*にポインティングDerivedオブジェクト、delete b持っている未定義の動作を

[ delete b]では、削除するオブジェクトの静的タイプが動的タイプと異なる場合、静的タイプは削除するオブジェクトの動的タイプの基本クラスであり、静的タイプは仮想デストラクタまたは動作は未定義です。

ほとんどの実装では、デストラクタの呼び出しは非仮想コードのように解決されます。つまり、基本クラスのデストラクタは呼び出されますが、派生クラスのデストラクタは呼び出されず、リソースリークが発生します。

要約すると、基本クラスvirtualが多態的に操作される場合は、常に基本クラスのデストラクタを作成します。

基本クラスポインターを通じてインスタンスが削除されないようにしたい場合は、基本クラスのデストラクターを保護し、非仮想にすることができます。そうすることで、コンパイラーはdelete基本クラスのポインターを呼び出せなくなります。

この記事では、Herb Sutterの仮想性と仮想基本クラスのデストラクターについて詳しく学ぶことができます。


174
これは、以前に作成した工場を使用して大量のリークがあった理由を説明しています。今ではすべてが理にかなっています。ありがとう
Lodle

8
データメンバーがないため、これは悪い例です。すべての自動ストレージ変数がある場合はどうBaseなりますか?つまり、デストラクタで実行する「特別な」または追加のカスタムコードはありません。それでは、デストラクタを書くのをやめても大丈夫ですか?それとも、派生クラスにはまだメモリリークがありますか?Derived
bobobobo


28
Herb Sutterの記事から:「ガイドライン#4:基本クラスのデストラクタは、パブリックかつ仮想であるか、保護されていて非仮想である必要があります。」
サンデー2016

3
また、記事から「仮想デストラクタなしでポリモーフィックに削除すると、「未定義の動作」という恐ろしい幽霊が召喚されます。私は個人的に、中程度の明るい照明の路地でさえ会いたくありません。どうもありがとうございました。」笑
ボンドリン

219

仮想コンストラクタは不可能ですが、仮想デストラクタは可能です。実験してみましょう……

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

上記のコードは以下を出力します:

Base Constructor Called
Derived constructor called
Base Destructor called

派生オブジェクトの構築は構築規則に従いますが、「b」ポインタ(ベースポインタ)を削除すると、ベースデストラクタのみが呼び出されることがわかりました。しかし、これは起こらないはずです。適切な処理を行うには、ベースデストラクタを仮想化する必要があります。ここで、次のことを見てみましょう。

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

出力は次のように変更されました。

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

したがって、ベースポインタの破棄(派生オブジェクトへの割り当てが必要です!)は破棄ルールに従います。つまり、最初にDerived、次にBaseとなります。一方、仮想コンストラクタのようなものはありません。


1
「仮想コンストラクタは不可能」とは、自分で仮想コンストラクタを記述する必要がないことを意味します。派生オブジェクトの構築は、派生からベースへの構築のチェーンに従う必要があります。したがって、コンストラクタの仮想キーワードを記述する必要はありません。ありがとう
Tunvir Rahman Tusher 2013

4
@Murkantilism、「仮想コンストラクタは実行できない」は確かに真実です。コンストラクターを仮想としてマークすることはできません。
cmeub 2013

1
@cmeub、しかし、仮想コンストラクタからあなたが望むものを達成するためのイディオムがあります。parashift.com/c++-faq-lite/virtual-ctors.htmlを
cape1232

@TunvirRahmanTusherベースデストラクタが呼び出される理由を説明していただけませんか?
rimalonfire 2017

@rimiro c ++による自動化。リンクstackoverflow.com/questions/677620/…を
Tunvir Rahman Tusher

195

ポリモーフィック基本クラスで仮想デストラクタを宣言します。これは、Scott MeyersのEffective C ++のアイテム7 です。マイヤーズクラスが持っている場合は、その要約することになります任意の仮想関数を、それが仮想デストラクタを持っており、多形使用されるように設計された基底クラスまたはないように設計されていないクラスがすべきことをすべきではない仮想デストラクタを宣言します。


14
+「クラスに仮想関数がある場合、そのクラスには仮想デストラクタが必要です。基本クラスとして設計されていない、または多態的に使用するように設計されていないクラスは、仮想デストラクタを宣言しないでください。」:このルールを破る?そうでない場合は、コンパイラーがこの条件をチェックしてエラーが発生することは理にかなっていますか?
ジョルジオ

@ジョルジオ私はルールの例外を知りません。しかし、私はC ++の専門家として自分を評価しないので、これを別の質問として投稿することをお勧めします。コンパイラの警告(または静的分析ツールからの警告)は私には理にかなっています。
リザードに請求

10
クラスは、特定の型のポインタを介して削除されないように設計できますが、仮想関数がまだあります-典型的な例はコールバックインターフェイスです。コールバックインターフェイスポインターを介して彼の実装を削除することはできません。これはサブスクライブするためだけのものですが、仮想関数を持っています。
dascandy

3
@dascandyちょうど-その、またはすべての多くの私たちが多型の挙動を使用しますが、ポインタを介してストレージ管理を実行していない他の状況-例えばのみ観測ルートとして使用するポインタを、自動または静的期間のオブジェクトを維持します。このような場合に仮想デストラクタを実装する必要はありません。ここでは単に人を引用しているだけなので、上からSutterを選びます。後者は、ベースポインターを介して誤って削除しようとするすべての
underscore_d

1
@Giorgioデストラクタへの仮想呼び出しを使用して回避できるトリックが実際にあります。const参照を介して、派生オブジェクトをベースにバインドしますconst Base& = make_Derived();。この場合、Derivedprvalueのデストラクタが呼び出されます。これが仮想でなくても、vtables / vpointersによって生じるオーバーヘッドを節約できます。もちろん範囲はかなり限定されています。Andrei Alexandrescuは、彼の著書「Modern C ++ Design」でこれについて述べています。
vsoftco 2016年

46

また、仮想デストラクタがないときに基本クラスポインタを削除すると、未定義の動作が発生することにも注意してください。私が最近学んだこと:

C ++でのオーバーライドの削除はどのように動作しますか?

私はC ++を何年も使用してきましたが、それでも何とかハングアップしています。


私はあなたのその質問を見て、あなたがベースデストラクタを仮想として宣言しているのを見ました。それで、「仮想デストラクタがないときに基本クラスポインタを削除すると、未定義の動作が発生する」というのは、あなたの質問に対して有効なままですか?その質問では、deleteを呼び出すと、派生クラス(そのnew演算子によって作成された)の互換性のあるバージョンが最初にチェックされるためです。そこで見つけたので呼ばれた。それで、「デストラクタがないときに基本クラスのポインタを削除すると、未定義の動作が発生する」と言った方がいいと思いませんか?
ubuntugod 2016

それはほとんど同じことです。デフォルトのコンストラクターは仮想ではありません。
BigSandwich 2016

41

クラスがポリモーフィックであるときはいつでも、デストラクターを仮想化します。


13

基本クラスへのポインタを介してデストラクタを呼び出す

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

仮想デストラクタ呼び出しは、他の仮想関数呼び出しと何の違いもありません。

の場合base->f()、呼び出しはにディスパッチされDerived::f()base->~Base()オーバーライド関数と同じです- Derived::~Derived()が呼び出されます。

デストラクタが間接的に呼び出されている場合も同様delete base;です。deleteステートメントが呼び出すbase->~Base()に派遣されますDerived::~Derived()

非仮想デストラクタを持つ抽象クラス

基本クラスへのポインタを介してオブジェクトを削除しない場合は、仮想デストラクタを用意する必要はありません。ただ、それを作るprotectedことが誤って呼び出されませんように。

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}

~Derived()すべての派生クラスで明示的に宣言する必要があり~Derived() = defaultますか?それとも、それは言語によって暗示されていますか(省略しても安全です)?
Ponkadoodle 2016

@Wallacolooいいえ、必要なときだけ宣言してください。たとえば、protectedセクションに配置したり、を使用して仮想であることを確認したりしoverrideます。
Abyx

9

インターフェースとインターフェースの実装について考えるのが好きです。C ++では、speakインターフェースは純粋な仮想クラスです。デストラクタはインターフェイスの一部であり、実装が期待されています。したがって、デストラクタは純粋に仮想である必要があります。コンストラクタはどうですか?オブジェクトは常に明示的にインスタンス化されるため、コンストラクターは実際にはインターフェイスの一部ではありません。


2
それは同じ質問に対する異なる見方です。基本クラスと派生クラスの代わりにインターフェイスの観点から考えると、それは自然な結論です。それが仮想化するよりもインターフェイスの一部である場合です。そうでない場合。
Dragan Ostojic

2
インターフェースのオブジェクト指向の概念とC ++の純粋な仮想クラスの類似性を示すための+1 。デストラクタに関しては、実装が予想されます。これは、多くの場合不要です。クラスが動的に割り当てられた生のメモリなどのリソースを管理していない限り(スマートポインターを介さないなど)、ファイルハンドルまたはデータベースハンドルは、コンパイラーによって作成されたデフォルトのデストラクターを使用して派生クラスで問題ありません。また、デストラクタ(または任意の関数)がvirtual基本クラスで宣言されvirtualている場合は、宣言されていなくても、自動的に派生クラスになります。
DavidRR 2013

これは、デストラクタが必ずしもインターフェースの一部ではないという重要な詳細を見逃しています。ポリモーフィック関数を持っているが、呼び出し側が管理していない/削除できないクラスを簡単にプログラムできます。その場合、仮想デストラクタには目的がありません。もちろん、これを確実にするために、非仮想(おそらくデフォルト)デストラクタは非公開である必要があります。推測しなければならないのですが、そのようなクラスはプロジェクトの内部でより頻繁に使用されると思いますが、それでも、これらすべての例/ニュアンスとしての関連性は低くなりません。
underscore_d

8

オブジェクトが基本クラスポインターを介して削除されている間、さまざまなデストラクターが適切な順序に従う必要がある場合は、デストラクターの仮想キーワードが必要です。例えば:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

基本クラスのデストラクタが仮想の場合、オブジェクトは順番に破棄されます(最初に派生したオブジェクト、次にbase)。基本クラスデストラクタが仮想でない場合、基本クラスオブジェクトのみが削除されます(ポインタが基本クラス "Base * myObj"であるため)。したがって、派生オブジェクトのメモリリークが発生します。


7

簡単に言うと、仮想デストラクタは、派生クラスオブジェクトを指す基本クラスポインタを削除するときに、リソースを適切な順序で破棄します。

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak


ベース仮想デストラクタがなくdelete、ベースポインタを呼び出すと、未定義の動作が発生します。
James Adkison、2016年

@JamesAdkisonなぜ未定義の動作につながるのですか?
rimalonfire 2017

@rimiro それは標準が言うことです。コピーはありませんが、リンクをクリックすると、標準内の場所を誰かが参照するコメントに移動します。
James Adkison 2017

@rimiro「したがって、もし削除が基本クラスのインターフェースを通じて多態的に実行できるなら、それは仮想的に振る舞わなければならず、仮想でなければなりません。実際、言語はそれを必要とします-仮想デストラクタなしで多態的に削除すると、恐ろしい幽霊を召喚します「未定義の振る舞い」、私が個人的に、適度に明るく照らされた路地でさえ会うのを嫌がる妖怪、ありがとうございました。」(gotw.ca/publications/mill18.htm)-ハーブサッター
James Adkison

4

仮想基本クラスのデストラクターは「ベストプラクティス」です。メモリーリークを回避する(検出するのが難しい)場合は、常にそれらを使用する必要があります。それらを使用すると、クラスの継承チェーン内のすべてのデストラクタが(適切な順序で)確実に呼び出されます。仮想デストラクタを使用して基本クラスから継承すると、継承クラスのデストラクタも自動的に仮想化されます(したがって、継承クラスデストラクタ宣言で「virtual」を再入力する必要はありません)。


4

使用する場合shared_ptr(unique_ptrではなくshared_ptrのみ)、基本クラスのデストラクタvirtual を使用する必要はありません。

#include <iostream>
#include <memory>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){ // not virtual
        cout << "Base Destructor called\n";
    }
};

class Derived: public Base
{
public:
    Derived(){
        cout << "Derived constructor called\n";
    }
    ~Derived(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    shared_ptr<Base> b(new Derived());
}

出力:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

これは可能ですが、私は誰もこれを使用しないようにします。仮想デストラクタのオーバーヘッドはごくわずかで、これにより、特にこれを知らない経験の浅いプログラマが混乱する可能性があります。その小さなvirtualキーワードはあなたを多くの苦痛から救うことができます。
MichalŠtein

3

仮想デストラクタとは何か、または仮想デストラクタの使用方法

クラスデストラクタは、クラスによって割り当てられたメモリを再割り当てする〜で始まるクラスと同じ名前の関数です。仮想デストラクタが必要な理由

いくつかの仮想関数を含む次のサンプルを参照してください

このサンプルは、文字を大文字または小文字に変換する方法も示しています

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

上記のサンプルから、MakeUpperクラスとMakeLowerクラスの両方のデストラクタが呼び出されていないことがわかります。

仮想デストラクタで次のサンプルを参照してください

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

仮想デストラクタは、クラスの最も派生した実行時デストラクタを明示的に呼び出し、適切な方法でオブジェクトをクリアできるようにします。

またはリンクにアクセスしてください

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138


2

基本クラスから派生クラスデストラクタを呼び出す必要がある場合。基本クラスで仮想基本クラスデストラクタを宣言する必要があります。


2

この質問の核心は、特に解体子ではなく、仮想メソッドと多態性に関するものだと思います。以下に、より明確な例を示します。

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

印刷されます:

This is B.

virtualそれがなければ印刷されます:

This is A.

そして、仮想デストラクタをいつ使用するかを理解する必要があります。


いいえ、これは仮想関数の完全な基本を再読するだけであり、デストラクタが1である必要がある理由/理由のニュアンスを完全に無視しています。これは直感的ではないため、OPが質問した理由です。?(また、なぜ不必要な動的な割り当ては、ここでだけ行うB b{}; A& a{b}; a.foo();。の確認NULLこれがあるべき- nullptr-前に、delete間違ったインデンデーションで- - INGの必要はありません:delete nullptr;。ノーオペレーションとして定義されているものは、あなたが呼び出す前に、これをチェックしている必要がある場合は->foo()、それ以外の場合は、new何らかの理由で失敗すると、未定義の動作が発生する可能性があります。)
underscore_d

2
呼び出しても安全であるdeleteNULL(つまり、あなたが必要のないポインタif (a != NULL)ガード)。
James Adkison、2016年

@SaileshDはい、知っています。それが私が私のコメントで
ジェームズ・アドキソン

1

私は、仮想デストラクタのない基本クラス(/ struct)を介して削除するときに発生する可能性がある「未定義」の動作、または少なくとも「クラッシュ」未定義の動作、またはより正確にはvtableについて説明することは有益だと思いました。以下のコードは、いくつかの単純な構造体をリストしています(同じことがクラスにも当てはまります)。

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

仮想デストラクタが必要かどうかは提案していませんが、一般的にはそれらを使用することをお勧めします。基本クラス(/ struct)にvtableがなく、派生クラス(/ struct)にあり、基本クラス(/ struct)を介してオブジェクトを削除した場合にクラッシュする可能性がある理由を指摘しているだけですポインタ。この場合、ヒープの空きルーチンに渡したアドレスは無効であるため、クラッシュの原因になります。

上記のコードを実行すると、問題がいつ発生するかが明確にわかります。基本クラス(/構造体)のthisポインターが派生クラス(/構造体)のthisポインターと異なる場合、この問題が発生します。上記のサンプルでは、​​構造体aとbにvtableがありません。struct cおよびdにはvtableがあります。したがって、acまたはdオブジェクトインスタンスへのaまたはbポインターは、vtableを考慮して修正されます。これを削除するためにaまたはbポインタを渡すと、ヒープの空きルーチンに対して無効なアドレスが原因でクラッシュします。

基本クラスポインターからvtableを持つ派生インスタンスを削除する場合は、基本クラスにvtableがあることを確認する必要があります。これを行う1つの方法は、仮想デストラクタを追加することです。仮想デストラクタを使用すると、リソースを適切にクリーンアップできます。


0

基本的な定義virtualは、クラスのメンバー関数を派生クラスでオーバーライドできるかどうかを決定することです。

クラスのD-torは基本的にスコープの最後で呼び出されますが、たとえば、ヒープ(動的割り当て)でインスタンスを定義するときに、手動で削除する必要があるという問題があります。

命令が実行されるとすぐに、基本クラスデストラクタが呼び出されますが、派生クラスのデストラクタは呼び出されません。

実用的な例は、制御フィールドでエフェクターやアクチュエーターを操作する必要がある場合です。

スコープの最後で、いずれかのパワー要素(アクチュエータ)のデストラクタが呼び出されない場合、致命的な結果が生じます。

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}

-1

ポリモーフィックであろうとなかろうと、パブリックに継承されるクラスには、仮想デストラクタが必要です。別の言い方をすれば、それが基本クラスポインタによってポイントされることができるならば、その基本クラスは仮想デストラクタを持つべきです。

仮想の場合、派生クラスのデストラクタが呼び出され、次に基本クラスのコンストラクタが呼び出されます。仮想でない場合は、基本クラスのデストラクタのみが呼び出されます。


これは、「基本クラスのポインタでポイントできる場合にのみ」必要であり、公に削除できる言えます。しかし、後で必要になる可能性がある場合に備えて、仮想dtorを追加する癖をつけても問題はないと思います。
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.