するために使用さ仮想destructors?
-
19-08-2019 - |
質問
私は確かな理解のもOOの理論がいつも一保堂茶舗東京丸の内店を混乱させてくれたのは仮想destructors.
そのデストラクタが呼ばれなど全てのオブジェクトをします。
ですって仮想か。
解決
仮想デストラクタは、基本クラスへのポインタを介して派生クラスのインスタンスを削除する可能性がある場合に役立ちます。
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!
Baseのデストラクタはb
ではなく、Base*
はDerived
オブジェクトを指すdelete b
であるため、delete
には未定義の動作:
[In <=>]、静的型の場合 削除されるオブジェクトは、その動的タイプ、静的 typeは、オブジェクトの動的な型の基本クラスです。 削除され、静的タイプには仮想デストラクタまたは 動作は未定義です。
ほとんどの実装では、デストラクタの呼び出しは非仮想コードと同様に解決されます。つまり、派生クラスのデストラクタではなくベースクラスのデストラクタが呼び出され、リソースリークが発生します。
要約すると、ベースクラスのデストラクタは、ポリモーフィックに操作する場合は常に<=>にしてください。
ベースクラスポインターを介したインスタンスの削除を防止する場合は、ベースクラスデストラクターを保護された非仮想にすることができます。そうすることで、コンパイラーは基本クラスのポインターで<=>を呼び出せなくなります。
他のヒント
仮想コンストラクターは使用できませんが、仮想デストラクターは使用できます。 実験してみましょう...
#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
派生オブジェクトの構築は構築ルールに従いますが、<!> quot; b <!> quot; pointer(base pointer)ベースデストラクタのみが呼び出されることがわかりました。しかし、これは起こらないはずです。適切なことを行うには、基本デストラクタを仮想化する必要があります。 次に、以下で何が起こるか見てみましょう:
#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 ++ のItem 7です。 Meyersは、クラスに仮想関数が any ある場合、仮想デストラクタが必要であり、基本クラスとして設計されていないか、多態的に使用されるように設計されていないクラスは not 仮想デストラクタを宣言します。
また、仮想デストラクタがないときにベースクラスポインタを削除すると、 未定義の動作 が発生することに注意してください。私が最近学んだこと:
私は長年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
ステートメントは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.
}
インターフェイスとインターフェイスの実装について考えるのが好きです。 C ++の話すインターフェイスは純粋な仮想クラスです。デストラクタはインターフェイスの一部であり、実装される予定です。したがって、デストラクタは純粋仮想である必要があります。コンストラクタはどうですか?オブジェクトは常に明示的にインスタンス化されるため、コンストラクタは実際にはインターフェイスの一部ではありません。
簡単にするために、 仮想デストラクタは、派生クラスオブジェクトを指すベースクラスポインタを削除するときに、適切な順序でリソースを破壊します。
#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
オブジェクトが基本クラスポインターを介して削除されている間、異なるデストラクタが適切な順序に従う必要がある場合、デストラクタの仮想キーワードが必要です。 例:
Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ;
派生クラスのデストラクタが仮想の場合、オブジェクトは順番に(最初に派生したオブジェクト、次にbaseの順に)破棄されます。派生クラスのデストラクタが仮想ではない場合、ベースクラスオブジェクトのみが削除されます(ポインタがベースクラス<!> quot; Base * myObj <!> quot;であるため)。そのため、派生オブジェクトのメモリリークが発生します。
何を仮想デストラクタの使い方の仮想デストラクタ
クラスデストラクタの関数と同じ名前のクラス前~その再配分のメモリ割り当てられるのです。なぜ必要としています仮想デストラクタ
以下のサンプルと仮想機能
サンプルもお伝えできる方法で変換し文字を上限下限
#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;
}
仮想デストラクタコンを明示的に最も由来の走行時間のデストラクタのクラスではかることができるものとのオブジェクト。
のリンク
仮想ベースクラスデストラクターは<!> quot;ベストプラクティス<!> quot; -常にそれらを使用して、メモリリーク(検出が困難)を回避する必要があります。それらを使用すると、クラスの継承チェーン内のすべてのデストラクタが(適切な順序で)呼び出されていることを確認できます。仮想デストラクタを使用して基本クラスから継承すると、継承クラスのデストラクタも自動的に仮想化されます(したがって、継承クラスのデストラクタ宣言で「仮想」を再入力する必要はありません)。
と思って有益なので"未定義"の行動、または少なくとも、"クラッシュ"未定義の動作中に発生する可能性がある削除する通の基底クラス(/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;
*/
}
いないことが必要かどうか仮想destructorsはないかと思う一般的で実践しています。私は指摘されている理由がクラッシュがラスの基底クラス(/structなvtable、自由来のクラス/struct)は、ファイルを選択し削除するオブジェクトを介して基底クラス(/struct)ポインタです。この場合、アドレス宛にパスへのヒープフルーチンが無効であることの理由ンダリングする能力があります。
行った場合、上記のコードする場合の問題が発生します。場合にこのポインタの基底クラス(/struct)とは異なるこのポインタの導出クラス(/struct)す込めます。に上記サンプルでは、struct、aとbはないvtables.構造体はcとdでないvtables.このようにaまたはbタイムスタンcまたはdのオブジェクトインスタンス行うことができるとのことである最大のvtable.きのこはポインタを消したりすることができますの衝突によるアドレスが無効になるエディタで開き、ヒープの無料。
される場合は削除由来のものインスタンスをvtablesから基底クラスへのポインタを確保する必要があり、基底クラスではvtable.方法が確立されてきているので、これまで仮想デストラクタは、いかに適切な清掃。
この質問の核心は、具体的なデストラクタではなく、仮想メソッドと多型性に関するものだと思います。より明確な例を次に示します。
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.
そして、仮想デストラクタを使用するタイミングを理解する必要があります。
基本クラスから派生クラスデストラクターを呼び出す必要がある場合。基本クラスで仮想基本クラスデストラクタを宣言する必要があります。
shared_ptr
(unique_ptrではなくshared_ptrのみ)を使用する場合、基本クラスのデストラクターを仮想にする必要はありません:
#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
ポリモーフィックかどうかにかかわらず、パブリックに継承されるクラスには、仮想デストラクタが必要です。別の言い方をすれば、基本クラスポインターでポイントできる場合、その基本クラスには仮想デストラクタが必要です。
仮想の場合、派生クラスのデストラクタが呼び出され、次に基本クラスのコンストラクタが呼び出されます。仮想でない場合、基本クラスのデストラクターのみが呼び出されます。