C++学習(5)-オブジェクト、参照、ポインタを使用して虚関数を呼び出す


虚関数実現原理説明:
                    
各クラスのサイズは、すべてのメンバーデータより4バイト多く、虚関数のあるクラスのサイズに圧縮された空のポインタタイプのサイズが加わることを示します.これは,虚関数を含むクラスにおいて,コンパイルシステムがタイプを示す情報を自動的にいくつか追加することを示している.
クラスに虚関数がある場合、コンパイルシステムはクラスの配列VTABLEを作成します.VTABLEの要素は虚関数のアドレスであり、同じ虚関数のアドレスはベースクラスと派生クラスのVTABLEにおける相対的なヘッダ位置のオフセットが同じである.また、コンパイルシステムには、対応する呼び出し虚関数のコードも追加されています.これらはすべてプログラマーの作業を必要とせず、システムによって自動的に完了します.このクラスオブジェクトを初期化すると、VTRと呼ばれるVTABLEを指すポインタが追加されます.一般にVTRは、そのクラスオブジェクトのメモリセルの先頭に位置する.
                           
このように、VTRが正しく初期化されると、オブジェクトのVTABLEが指し示され、オブジェクトとその特定の虚関数定義との間に関連が確立される.虚関数呼び出しの意味で、VTRは、呼び出しがタイプに一致するため、タイプ情報を示す.
 
この例(以下を参照)では、クラス階層がどれだけ拡張されても、既存のクラスオブジェクトの操作を変更する必要はありません.この点から,虚関数が体現する実行時多態は,ソフトウェアの拡張性を大幅に向上させた.システムの設計者は初期にシステムフレームワーク全体の合理的な構築に集中し,後期に具体的な問題の分析を行い,このフレームワークを徐々に拡張することを知っている.実行時のマルチステートは、分析、設計、実現、拡張の各段階の統一を保証し、システムの設計者が各段階で目の前の仕事に集中することができ、後で予見できない変化のために代価を払う必要はありません.
 
虚関数のまとめ:
      
虚関数はクラスメンバー関数のみであり、ベースクラス内で説明され、インタフェースインタフェースを提供することを目的としています.

虚関数は、特定のオブジェクトによってアクティブ化される関数を決定するため、友元関数(すなわち非メンバー関数)でも静的メンバー関数でもない.虚関数は、別のクラスで友元関数として宣言できます.
1つの関数が虚関数として定義されると、それがどの層に伝わっても、毎回キーワードvirtualを追加する必要がなく、虚関数として維持されます.
                        
ベースクラスの虚関数は1つ以上の派生クラスで再定義できるが、その原型はベースクラスと完全に同じでなければならない(すなわち、戻りタイプ、関数名、パラメータの個数、タイプ及び順序が同じである).そうでなければ、システムは派生クラスの関数が虚関数ではなく、リロードされていると考える.戻りタイプのみが異なる場合、コンパイルはエラーになります.
                          
虚関数が機能するには、派生クラスのオブジェクトをベースクラスのポインタ(または参照)で指し、虚関数をポインタ(または参照)で呼び出す必要があります.すなわち,実行多様性はアドレスのみで表現できる.ベースクラスを指すポインタ(参照)と派生クラスを指すポインタ(参照)は同じサイズなので、ベースクラスポインタで派生クラスオブジェクトを指すことができます.この場合,ポインタが提供する情報は不完全であり,コンパイル段階では虚関数のどのバージョンを呼び出すべきか分からない.オブジェクトで虚関数を呼び出すと、タイプが決定されたため、コンパイルシステムはプリバインドを採用する可能性が高い.
                                                      
虚関数を含むベースクラスポインタは、その異なる派生クラスを指すことができ、異なるバージョンの虚関数を実行することができ、プログラムの実行を実現するマルチステートメソッドを提供するため、虚関数を含むクラスをマルチステートクラスと呼ぶ.
                   
虚関数と一般的なリロード関数の違い:
                              
リロード関数はタイプとパラメータの数が異なるに違いないが、再定義された虚関数はパラメータのタイプと個数、関数の戻りタイプが同じであることを要求する.
虚関数はクラスのメンバー関数でなければならないが、再ロードされた関数は必ずしもそうではない.
コンストラクション関数はリロードできますが、虚関数ではなく、コンストラクション関数は虚関数であることができます.
                     
ポインタの変換規則:
                        
ベースクラスへのポインタは、その共通派生オブジェクトを指すことができますが、プライベート派生オブジェクトを指すことはできません.
これを利用して派生クラスがベースクラスから継承したメンバーに直接アクセスすることができ、公有派生クラスの特定のメンバーに直接アクセスすることができない.
派生クラスオブジェクトへのポインタをそのベースクラスの1つのオブジェクトに向けることはできません.
                             
操作で参照されたベースクラスのデータ・メンバーが派生クラスによって直接参照できない場合(非表示のメンバーなど)、エラーが発生します.虚関数を使用して最良のダイナミックアセンブリ効果を達成するには、一般的に、この虚関数が初めて現れるクラスの参照体またはポインタをパラメータとして、不確定な要因を回避する必要があります.                        
#include<iostream>
#include<stdio.h>
using namespace std;
//  
class CBase
{
    int x;
public:
    CBase(int n) {x=n;}
    virtual void PrintX() {cout<<"CBase::PrintX : "<<x<<endl;}
};

//   
class CDerive : public CBase
{
    int x;
public:
    CDerive(int n1,int n2):CBase(n1)
{    x=n2;    }
    void PrintX()
    {    cout<<"CDerive::PrintX : "<<x<<endl;
        CBase::PrintX();
    }
};

//    
class CSubDerive : public CDerive
{
    int x;
public:
    CSubDerive(int n1,int n2,int n3):CDerive(n1,n2)
    {    x=n3;    }
    void PrintX()
    {    cout<<"CSubDerive::PrintX : "<<x<<endl;
        CDerive::PrintX();
    }
};

int main()
{   cout<<"CBase size = "<<sizeof(CBase)<<endl;
    cout<<"CDerive size = "<<sizeof(CDerive)<<endl;
    cout<<"CSubDerive size = "<<sizeof(CSubDerive)<<endl;
    cout<<endl;

    CBase obj1(1);
CDerive obj2(2,3);
CSubDerive obj3(4,5,6);
    obj1.PrintX();
    cout<<endl;
    obj2.PrintX();
    cout<<endl;
    obj3.PrintX();
    cout<<endl;

    CBase *pObj1=&obj1;
    CDerive *pObj2=&obj2;
    CSubDerive *pObj3=&obj3;
    pObj1->PrintX();
    cout<<endl;
    pObj2->PrintX();
    cout<<endl;
    pObj3->PrintX();
    cout<<endl;

    CBase *pObj[]={pObj1,pObj2,pObj3};
    pObj[0]->PrintX();
    cout<<endl;
    pObj[1]->PrintX();
    cout<<endl;
    pObj[2]->PrintX();
    cout<<endl;

    CBase &yobj1=obj1;
    CBase &yobj2=obj2;
    CBase &yobj3=obj3;
    yobj1.PrintX();
    cout<<endl;
    yobj2.PrintX();
    cout<<endl;
    yobj3.PrintX();
    cout<<endl;
     return 0;
}