私はほとんどのオブジェクト指向理論をしっかりと理解していますが、私を混乱させるのは仮想デストラクタです。
デストラクタは、チェーン内のすべてのオブジェクトに関係なく、常に呼び出されると思いました。
いつ仮想化するつもりですか?なぜですか?
virtual
中央ではなく上部から始まることを確認します。
私はほとんどのオブジェクト指向理論をしっかりと理解していますが、私を混乱させるのは仮想デストラクタです。
デストラクタは、チェーン内のすべてのオブジェクトに関係なく、常に呼び出されると思いました。
いつ仮想化するつもりですか?なぜですか?
virtual
中央ではなく上部から始まることを確認します。
回答:
仮想デストラクタは、基本クラスへのポインタを介して派生クラスのインスタンスを削除する可能性がある場合に役立ちます。
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の仮想性と仮想基本クラスのデストラクターについて詳しく学ぶことができます。
Base
なりますか?つまり、デストラクタで実行する「特別な」または追加のカスタムコードはありません。それでは、デストラクタを書くのをやめても大丈夫ですか?それとも、派生クラスにはまだメモリリークがありますか?Derived
仮想コンストラクタは不可能ですが、仮想デストラクタは可能です。実験してみましょう……
#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となります。一方、仮想コンストラクタのようなものはありません。
ポリモーフィック基本クラスで仮想デストラクタを宣言します。これは、Scott MeyersのEffective C ++のアイテム7 です。マイヤーズクラスが持っている場合は、その要約することになります任意の仮想関数を、それが仮想デストラクタを持っており、多形使用されるように設計された基底クラスまたはないように設計されていないクラスがすべきことをすべきではない仮想デストラクタを宣言します。
const Base& = make_Derived();
。この場合、Derived
prvalueのデストラクタが呼び出されます。これが仮想でなくても、vtables / vpointersによって生じるオーバーヘッドを節約できます。もちろん範囲はかなり限定されています。Andrei Alexandrescuは、彼の著書「Modern C ++ Design」でこれについて述べています。
また、仮想デストラクタがないときに基本クラスポインタを削除すると、未定義の動作が発生することにも注意してください。私が最近学んだこと:
私はC ++を何年も使用してきましたが、それでも何とかハングアップしています。
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
ますか?それとも、それは言語によって暗示されていますか(省略しても安全です)?
protected
セクションに配置したり、を使用して仮想であることを確認したりしoverride
ます。
インターフェースとインターフェースの実装について考えるのが好きです。C ++では、speakインターフェースは純粋な仮想クラスです。デストラクタはインターフェイスの一部であり、実装が期待されています。したがって、デストラクタは純粋に仮想である必要があります。コンストラクタはどうですか?オブジェクトは常に明示的にインスタンス化されるため、コンストラクターは実際にはインターフェイスの一部ではありません。
virtual
基本クラスで宣言されvirtual
ている場合は、宣言されていなくても、自動的に派生クラスになります。
オブジェクトが基本クラスポインターを介して削除されている間、さまざまなデストラクターが適切な順序に従う必要がある場合は、デストラクターの仮想キーワードが必要です。例えば:
Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ;
基本クラスのデストラクタが仮想の場合、オブジェクトは順番に破棄されます(最初に派生したオブジェクト、次にbase)。基本クラスデストラクタが仮想でない場合、基本クラスオブジェクトのみが削除されます(ポインタが基本クラス "Base * myObj"であるため)。したがって、派生オブジェクトのメモリリークが発生します。
簡単に言うと、仮想デストラクタは、派生クラスオブジェクトを指す基本クラスポインタを削除するときに、リソースを適切な順序で破棄します。
#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
、ベースポインタを呼び出すと、未定義の動作が発生します。
使用する場合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
キーワードはあなたを多くの苦痛から救うことができます。
仮想デストラクタとは何か、または仮想デストラクタの使用方法
クラスデストラクタは、クラスによって割り当てられたメモリを再割り当てする〜で始まるクラスと同じ名前の関数です。仮想デストラクタが必要な理由
いくつかの仮想関数を含む次のサンプルを参照してください
このサンプルは、文字を大文字または小文字に変換する方法も示しています
#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;
}
仮想デストラクタは、クラスの最も派生した実行時デストラクタを明示的に呼び出し、適切な方法でオブジェクトをクリアできるようにします。
またはリンクにアクセスしてください
基本クラスから派生クラスデストラクタを呼び出す必要がある場合。基本クラスで仮想基本クラスデストラクタを宣言する必要があります。
この質問の核心は、特に解体子ではなく、仮想メソッドと多態性に関するものだと思います。以下に、より明確な例を示します。
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.
そして、仮想デストラクタをいつ使用するかを理解する必要があります。
B b{}; A& a{b}; a.foo();
。の確認NULL
これがあるべき- nullptr
-前に、delete
間違ったインデンデーションで- - INGの必要はありません:delete nullptr;
。ノーオペレーションとして定義されているものは、あなたが呼び出す前に、これをチェックしている必要がある場合は->foo()
、それ以外の場合は、new
何らかの理由で失敗すると、未定義の動作が発生する可能性があります。)
delete
上NULL
(つまり、あなたが必要のないポインタif (a != NULL)
ガード)。
私は、仮想デストラクタのない基本クラス(/ 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つの方法は、仮想デストラクタを追加することです。仮想デストラクタを使用すると、リソースを適切にクリーンアップできます。
基本的な定義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;
}
ポリモーフィックであろうとなかろうと、パブリックに継承されるクラスには、仮想デストラクタが必要です。別の言い方をすれば、それが基本クラスポインタによってポイントされることができるならば、その基本クラスは仮想デストラクタを持つべきです。
仮想の場合、派生クラスのデストラクタが呼び出され、次に基本クラスのコンストラクタが呼び出されます。仮想でない場合は、基本クラスのデストラクタのみが呼び出されます。