私はほとんどのOOP
理論をしっかりと理解していますが、私を非常に混乱させているのは仮想デストラクタです。
デストラクタは、何があっても、チェーン内のすべてのオブジェクトに対して常に呼び出されると思いました。
いつそれらを仮想化するつもりですか、またその理由は何ですか?
私はほとんどのOOP
理論をしっかりと理解していますが、私を非常に混乱させているのは仮想デストラクタです。
デストラクタは、何があっても、チェーン内のすべてのオブジェクトに対して常に呼び出されると思いました。
いつそれらを仮想化するつもりですか、またその理由は何ですか?
仮想デストラクタは、基本クラスへのポインタを介して派生クラスのインスタンスを削除する可能性がある場合に役立ちます。
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!
ベースのデストラクタはオブジェクトではなく、virtual
オブジェクトをb
指しBase*
ているため、未定義の動作があります:Derived
delete b
[
delete b
で]、削除するオブジェクトの静的型が動的型と異なる場合、静的型は削除するオブジェクトの動的型の基本クラスであり、静的型は仮想デストラクタまたは動作は未定義です。
ほとんどの実装では、デストラクタへの呼び出しは非仮想コードと同じように解決されます。つまり、基本クラスのデストラクタは呼び出されますが、派生クラスのデストラクタは呼び出されないため、リソース リークが発生します。
virtual
要約すると、ポリモーフィックに操作する場合は、常に基底クラスのデストラクタを作成します。
基底クラス ポインターを介してインスタンスが削除されないようにする場合は、基底クラスのデストラクタを保護された非仮想にすることができます。そうすることで、コンパイラはdelete
基本クラスのポインターを呼び出すことができなくなります。
Herb Sutter によるこの記事で、仮想性と仮想基本クラスのデストラクタについて詳しく学ぶことができます。
仮想コンストラクタは使用できませんが、仮想デストラクタは使用できます。実験してみましょう.......
#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
そのため、ベース ポインタ (派生オブジェクトで割り当てを取得します!) の破棄は、破棄規則に従います。つまり、最初に派生オブジェクト、次にベースです。一方、仮想コンストラクターのようなものはありません。
ポリモーフィックな基本クラスで仮想デストラクタを宣言します。これは Scott Meyers の「Effective C++ 」の項目 7 です。クラスに仮想関数がある場合、クラスには仮想デストラクタが必要であり、基本クラスとして設計されていないか、ポリモーフィックに使用されるように設計されていないクラスは、仮想デストラクタを宣言しないでください。
また、仮想デストラクタがない場合に基底クラス ポインタを削除すると、未定義の動作が発生することにも注意してください。私が最近学んだこと:
C++ での削除のオーバーライドはどのように動作する必要がありますか?
私は何年も C++ を使用してきましたが、いまだに首を吊ってしまいます。
クラスがポリモーフィックである場合は常に、デストラクタを仮想にします。
インターフェイスとインターフェイスの実装について考えるのが好きです。C++ では、インターフェイスは純粋な仮想クラスです。デストラクタはインターフェースの一部であり、実装される予定です。したがって、デストラクタは純粋仮想でなければなりません。コンストラクタはどうですか?オブジェクトは常に明示的にインスタンス化されるため、コンストラクターは実際にはインターフェイスの一部ではありません。
この質問の核心は、特にデストラクタではなく、仮想メソッドとポリモーフィズムに関するものだと思います。より明確な例を次に示します。
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.
これで、仮想デストラクタをいつ使用するかを理解する必要があります。
仮想基本クラスのデストラクタは「ベスト プラクティス」です。(検出が困難な) メモリ リークを回避するために、常にそれらを使用する必要があります。それらを使用すると、クラスの継承チェーン内のすべてのデストラクタが (適切な順序で) 呼び出されていることを確認できます。仮想デストラクタを使用して基本クラスから継承すると、継承クラスのデストラクタも自動的に仮想になります (したがって、継承クラスのデストラクタ宣言で「virtual」と再入力する必要はありません)。
仮想デストラクタとは何か、または仮想デストラクタの使用方法
クラスデストラクタは、クラスによって割り当てられたメモリを再割り当てする ~ で始まるクラスと同じ名前の関数です。仮想デストラクタが必要な理由
いくつかの仮想関数を含む次のサンプルを参照してください
サンプルは、文字を大文字または小文字に変換する方法も示しています
#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;
}
仮想デストラクタは、クラスの最も派生した実行時デストラクタを明示的に呼び出して、適切な方法でオブジェクトをクリアできるようにします。
または、リンクにアクセスしてください
基本クラスから派生クラスのデストラクタを呼び出す必要がある場合。基本クラスで仮想基本クラスのデストラクタを宣言する必要があります。
ポリモーフィックであろうとなかろうと、パブリックに継承されるすべてのクラスには、仮想デストラクタが必要です。別の言い方をすれば、基底クラスのポインターで指すことができる場合、その基底クラスには仮想デストラクタが必要です。
仮想の場合、派生クラスのデストラクタが呼び出され、次に基本クラスのデストラクタが呼び出されます。仮想でない場合は、基本クラスのデストラクタのみが呼び出されます。