ベースクラスの解析関数は虚関数と書く必要がありますか?
3980 ワード
私は以前から、ベースクラスの構造関数は虚関数であるべきだと思っていました.そうしないと、構造時の危険性が残り、他の虚関数がない場合、dynamic_castは動作しません.
例を挙げると、以下のように、ベースクラスBaseはインスタンス化されたオブジェクトを識別するために一意のIDを提供するだけであり、他の虚関数を使用する必要はありません.
まず、隠れた危険性があります.
何が出力されますか?
DerivedA::~DerivedA() Base::~Base()
DerivedAobjの構造関数は呼び出されていません.クラスの構造関数が実際にスタックに割り当てられたメモリをクリーンアップしたと仮定すると、このような危険性によりメモリが漏洩します.
次にこの時、私には方法があります.
このときコンパイルにエラーメッセージがあります:error C 2683:'dynamic_cast' : 'DerivedA' is not a polymorphic type
このエラーは、DerivedAはマルチステートタイプではなくdynamic_を使用できないということです.castを動的に変換します.知ってるよdynamiccastの動的変換は,虚表に存在するRTTIに依存する.ただし、ここでは継承に虚関数は使用されていないため、実行時にタイプ情報を取得できません.
では、ベースクラス、例えばBaseの構造関数にvirtualを付けなければならないのではないでしょうか.
Base::~Base()が虚関数であれば,まずサブクラスオブジェクトのポインタがベースクラスポインタに変換されてから削除されると,依然として解析操作が正しく行われる.
たとえば、上の出力は次のようになります.
DerivedAObj::~DerivedAObj() DerivedA::~DerivedA() Base::~Base()
次にdynamic_castも正しく動作します.
これはいずれもベースクラスが必ず虚析构関数を使用する理由になっているようだが,需要のないベースクラスに虚析构関数を设置する必要がないという他の理由もある.
たとえばBaseでは、ClassIDが1つしか提供されていません.仮想構造関数を宣言すると、継承されたすべてのサブクラスが独自の仮想テーブルを持っていることを示します.特に、継承されたDerivedBはデータクラスにすぎないかもしれませんが、独自の仮想テーブルを構築する必要があります.これは明らかにパフォーマンスが低下します.
一方、マルチステートのニーズは、BaseのサブクラスDerivedAのような継承チェーンの先端から始まるわけではありません.このレイヤからマルチステートの特性が使用される可能性があります.では、このノードから仮想構造関数を宣言することができます.ベースクラスではありません.Baseクラスに継承された他のサブクラス(DerivedBなど)は、虚表を作成するためにパフォーマンスを消費する必要はありません.
以上,正確な構造解析の問題を解決するために,DerivedA:~DerivedA()を虚構造関数と宣言する.
則
正しく出力されます.
DerivedAObj::~DerivedAObj()
DerivedA::~DerivedA()
Base::~Base()
では、このように使えば?
残念なことに、これは出力されます.
Base::~Base()
どうして?~Base()は虚関数ではないからです.
葛藤しますか?実は葛藤しない.性能上、Baseが虚構造関数を使用する必要がない場合は、Baseの構造関数をprivateとして定義し、Baseのdelete操作を防止しなければならない.使用者がこのようにしようとすると、コンパイラはエラーを報告し、使用者に正しい使用対象を強制し、隠れた危険を防ぐ.
まだ問題があるdynamic_キャストは?
実はdynamic_castはRTTIに依存するが,RTTIはすべての場合にオンであるわけではないので,本来安全な変換は必ずしも安全ではない.もしあなたが最下位のライブラリを書いているならば、あなたはそれがどこで使うか分からないで、その場所がRTTIを開くかどうか、あるいはサポートするかどうか、そのため、もっと安全な方法は自分でRTTIのメカニズムを実現することです.
一般的に、デフォルトではRTTI,dynamic_がオンになっています.castは虚表のRTTI情報に依存し,虚関数が定義されていない場合,コンパイラはエラーを報告する.そしてdynamic_キャストは菱形/cross継承などの面でまだ問題があるのでdynamic_castは「必ずしも安全ではない」変換です.
結論:
もちろん、これは賢明な見方であり、通常、動的変換に多く使用されていますが、特定のコンポーネントや下位層を書く際には、RTTIの実現問題を慎重に考慮する必要があります.
ベースクラスがダミー構造関数を定義するかどうかの問題では、パフォーマンスに注目せずにダミー構造関数を定義することでdeleteの危険性を回避できます.同様に、パフォーマンスに注目する場合、仮想構造関数の階層を定義していない場合は、構造関数をプライベート化し、隠れたバグを防止することに注意する必要があります.
例を挙げると、以下のように、ベースクラスBaseはインスタンス化されたオブジェクトを識別するために一意のIDを提供するだけであり、他の虚関数を使用する必要はありません.
typedef long ClassID;
ClassID gID;
class Base
{
public:
Base() { mClassID = gID++; }
~Base() { printf("Base::~Base()
"); }
public:
ClassID getClassID() { return mClassID; }
private:
ClassID mClassID;
};
class DerivedA : public Base
{
public:
DerivedA() { mX = mY = 0; }
~DerivedA() { printf("DerivedA::~DerivedA()
"); }
public:
int getX() { return mX; }
void setX( int x ) { mX = x; }
int getY() { return mY; }
void setY( int y ) { mY = y; }
private:
int mX;
int mY;
};
class DerivedAObj : public DerivedA
{
public:
DerivedAObj() { mRaduis = 0; }
~DerivedAObj() { printf("DerivedAObj::~DerivedAObj()
"); }
public:
int getRaduis() { return mRaduis; }
void setRaduis( int raduis ) { mRaduis = raduis; }
private:
int mRaduis;
};
まず、隠れた危険性があります.
DerivedAObj* pAObj = new DerivedAObj;
DerivedA* pA = pAObj;
delete pA;
何が出力されますか?
DerivedA::~DerivedA() Base::~Base()
DerivedAobjの構造関数は呼び出されていません.クラスの構造関数が実際にスタックに割り当てられたメモリをクリーンアップしたと仮定すると、このような危険性によりメモリが漏洩します.
次にこの時、私には方法があります.
void printRaduis( DerivedA* pObj )
{
DerivedAObj* pAObj = dynamic_cast< DerivedAObj* >( pObj );
if( pAObj != NULL )
{
printf( "Raduis: %d
", pAObj->getRaduis() );
}
}
このときコンパイルにエラーメッセージがあります:error C 2683:'dynamic_cast' : 'DerivedA' is not a polymorphic type
このエラーは、DerivedAはマルチステートタイプではなくdynamic_を使用できないということです.castを動的に変換します.知ってるよdynamiccastの動的変換は,虚表に存在するRTTIに依存する.ただし、ここでは継承に虚関数は使用されていないため、実行時にタイプ情報を取得できません.
では、ベースクラス、例えばBaseの構造関数にvirtualを付けなければならないのではないでしょうか.
Base::~Base()が虚関数であれば,まずサブクラスオブジェクトのポインタがベースクラスポインタに変換されてから削除されると,依然として解析操作が正しく行われる.
たとえば、上の出力は次のようになります.
DerivedAObj::~DerivedAObj() DerivedA::~DerivedA() Base::~Base()
次にdynamic_castも正しく動作します.
これはいずれもベースクラスが必ず虚析构関数を使用する理由になっているようだが,需要のないベースクラスに虚析构関数を设置する必要がないという他の理由もある.
class DerivedB : public Base
{
public:
DerivedB() { }
~DerivedB() { }
//Data block
};
たとえばBaseでは、ClassIDが1つしか提供されていません.仮想構造関数を宣言すると、継承されたすべてのサブクラスが独自の仮想テーブルを持っていることを示します.特に、継承されたDerivedBはデータクラスにすぎないかもしれませんが、独自の仮想テーブルを構築する必要があります.これは明らかにパフォーマンスが低下します.
一方、マルチステートのニーズは、BaseのサブクラスDerivedAのような継承チェーンの先端から始まるわけではありません.このレイヤからマルチステートの特性が使用される可能性があります.では、このノードから仮想構造関数を宣言することができます.ベースクラスではありません.Baseクラスに継承された他のサブクラス(DerivedBなど)は、虚表を作成するためにパフォーマンスを消費する必要はありません.
以上,正確な構造解析の問題を解決するために,DerivedA:~DerivedA()を虚構造関数と宣言する.
virtual ~DerivedA() { printf("DerivedA::~DerivedA()
"); }
則
DerivedAObj* pAObj = new DerivedAObj;
DerivedA* pA = pAObj;
delete pA;
正しく出力されます.
DerivedAObj::~DerivedAObj()
DerivedA::~DerivedA()
Base::~Base()
では、このように使えば?
DerivedAObj* pAObj = new DerivedAObj;
Base* pB = pAObj;
delete pB;
残念なことに、これは出力されます.
Base::~Base()
どうして?~Base()は虚関数ではないからです.
葛藤しますか?実は葛藤しない.性能上、Baseが虚構造関数を使用する必要がない場合は、Baseの構造関数をprivateとして定義し、Baseのdelete操作を防止しなければならない.使用者がこのようにしようとすると、コンパイラはエラーを報告し、使用者に正しい使用対象を強制し、隠れた危険を防ぐ.
まだ問題があるdynamic_キャストは?
実はdynamic_castはRTTIに依存するが,RTTIはすべての場合にオンであるわけではないので,本来安全な変換は必ずしも安全ではない.もしあなたが最下位のライブラリを書いているならば、あなたはそれがどこで使うか分からないで、その場所がRTTIを開くかどうか、あるいはサポートするかどうか、そのため、もっと安全な方法は自分でRTTIのメカニズムを実現することです.
一般的に、デフォルトではRTTI,dynamic_がオンになっています.castは虚表のRTTI情報に依存し,虚関数が定義されていない場合,コンパイラはエラーを報告する.そしてdynamic_キャストは菱形/cross継承などの面でまだ問題があるのでdynamic_castは「必ずしも安全ではない」変換です.
結論:
もちろん、これは賢明な見方であり、通常、動的変換に多く使用されていますが、特定のコンポーネントや下位層を書く際には、RTTIの実現問題を慎重に考慮する必要があります.
ベースクラスがダミー構造関数を定義するかどうかの問題では、パフォーマンスに注目せずにダミー構造関数を定義することでdeleteの危険性を回避できます.同様に、パフォーマンスに注目する場合、仮想構造関数の階層を定義していない場合は、構造関数をプライベート化し、隠れたバグを防止することに注意する必要があります.