C++マルチステートの理解について


クラスのマルチステート特性はオブジェクト向け言語をサポートする最も主要な特性であり、非オブジェクト向け言語の開発経験がある人は、この章の内容に慣れていないことが多い.多くの人がクラスのパッケージをサポートする言語はオブジェクト向けをサポートするものだと勘違いしているが、実際にはそうではない.Visual BASIC 6.0は典型的な非オブジェクト向けの開発言語である.しかし、それは確かにサポートクラスであり、サポートクラスはオブジェクト向けをサポートし、多態問題を解決できる言語こそ、オブジェクト向けの開発を本当にサポートする言語であるとは説明できないので、他の非オブジェクト言語の基礎がある読者に注意しなければならない.多態のこの概念は少し曖昧で、最初からはっきりと言語で説明したいなら、読者に理解させたいが、現実的ではないようだ.
//ルーチン1
#include <iostream>      
using namespace std;    
    
class Vehicle 
{    
public:    
     Vehicle(float speed,int total) 
     { 
         Vehicle::speed=speed; 
         Vehicle::total=total; 
     } 
    void ShowMember() 
     { 
        cout<<speed<<"|"<<total<<endl; 
     } 
protected:    
    float speed; 
    int total; 
};    
class Car:public Vehicle    
{    
public:    
     Car(int aird,float speed,int total):Vehicle(speed,total)    
     {    
         Car::aird=aird;    
     } 

    
void ShowMember()ですが、実際の作業では、オブジェクトが属するクラスが不明な場合があります.次に、派生クラスメンバーが関数パラメータとして渡される例を見てみましょう.コードは以下の通りです.
//ルーチン2
#include <iostream>      
using namespace std;    
    
class Vehicle 
{    
public:    
     Vehicle(float speed,int total) 
     { 
         Vehicle::speed=speed; 
         Vehicle::total=total; 
     } 
    void ShowMember() 
     { 
        cout<<speed<<"|"<<total<<endl; 
     } 
protected:    
    float speed; 
    int total; 
};    
class Car:public Vehicle    
{    
public:    
     Car(int aird,float speed,int total):Vehicle(speed,total)    
     {    
         Car::aird=aird;    
     } 
    void ShowMember() 
     { 
        cout<<speed<<"|"<<total<<"|"<<aird<<endl; 
     } 
protected:    
    int aird; 
};    

void test(Vehicle &temp) 
{ 
     temp.ShowMember(); 
} 

void main()    
{ 
     Vehicle a(120,4); 
     Car b(180,110,4); 
     test(a); 
     test(b); 
    cin.get(); 
}

例では、オブジェクトaとbの分解はベースクラスと派生クラスのオブジェクトであり、関数testのパラメータはVehicleクラスの参照にすぎない.クラス継承の特徴に従って、システムはCarクラスのオブジェクトをVehicleクラスのオブジェクトと見なしている.Carクラスのカバー範囲にはVehicleクラスが含まれているため、test関数の定義は間違っていない.test関数を利用して達成したい目的は、異なるクラスのオブジェクトの参照を伝達し、それぞれ異なるクラスの、再ロードされた、ShowMemberメンバー関数を呼び出すが、プログラムの実行結果は予想外であり、システムは伝達されたベースクラスオブジェクトと派生クラスオブジェクトの区別がつかず、ベースクラスオブジェクトでも派生クラスオブジェクトでもベースクラスのShowMemberメンバー関数を呼び出す.
  { 
        cout<<speed<<"|"<<total<<"|"<<aird<<endl; 
     } 
protected:    
    int aird; 
};    

void main()    
{    
     Vehicle a(120,4); 
     a.ShowMember(); 
     Car b(180,110,4); 
     b.ShowMember(); 
    cin.get(); 
}

c++では派生クラスのベースクラスメンバー関数の再ロードが許可されているが、クラスの再ロードについては、明確に、異なるクラスのオブジェクトが、そのクラスのメンバー関数を呼び出すとき、システムは、そのクラスの同名メンバーを見つける方法を知っている.すなわち、Vehicle::ShowMember()、b.ShowMember()、すなわち、Car::ShowMemeber();.
しかし、実際の作業では、オブジェクトが属するクラスが不明な場合に遭遇する可能性があります.次に、派生クラスメンバーが関数パラメータとして渡される例を見てみましょう.コードは以下の通りです.
//ルーチン2
#include <iostream>      
using namespace std;    
    
class Vehicle 
{    
public:    
     Vehicle(float speed,int total) 
     { 
         Vehicle::speed=speed; 
         Vehicle::total=total; 
     } 
    void ShowMember() 
     { 
        cout<<speed<<"|"<<total<<endl; 
     } 
protected:    
    float speed; 
    int total; 
};    
class Car:public Vehicle    
{    
public:    
     Car(int aird,float speed,int total):Vehicle(speed,total)    
     {    
         Car::aird=aird;    
     } 
    void ShowMember() 
     { 
        cout<<speed<<"|"<<total<<"|"<<aird<<endl; 
     } 
protected:    
    int aird; 
};    

void test(Vehicle &temp) 
{ 
     temp.ShowMember(); 
} 

void main()    
{ 
     Vehicle a(120,4); 
     Car b(180,110,4); 
     test(a); 
     test(b); 
    cin.get(); 
}

例では、オブジェクトaとbの分解はベースクラスと派生クラスのオブジェクトであり、関数testのパラメータはVehicleクラスの参照にすぎない.クラス継承の特徴に従って、システムはCarクラスのオブジェクトをVehicleクラスのオブジェクトと見なしている.Carクラスのカバー範囲にはVehicleクラスが含まれているため、test関数の定義は間違っていない.test関数を利用して達成したい目的は、異なるクラスのオブジェクトの参照を伝達し、それぞれ異なるクラスの、再ロードされた、ShowMemberメンバー関数を呼び出すが、プログラムの実行結果は予想外であり、システムは伝達されたベースクラスオブジェクトと派生クラスオブジェクトの区別がつかず、ベースクラスオブジェクトでも派生クラスオブジェクトでもベースクラスのShowMemberメンバー関数を呼び出す.
上記のようにオブジェクトタイプを正確に見分けることができない問題を解決するために、c++は、コンパイル時にどのリロードされたメンバー関数が呼び出されるかを決定できるようにする先行コンパイル(early binding)と呼ばれ、システムが実行可能である場合、そのタイプに基づいて、マルチステートと呼ばれるどのリロードされたメンバー関数を呼び出す能力を決定するか、またはヒステリシス結合(late binding)と呼ぶことができます.次に、ルーチン3を見てみましょう.ヒステリシス結合であり、ヒステリシス結合はマルチステート問題を解決する方法です.コードは次のとおりです.
//ルーチン3
#include <iostream>      
using namespace std;    
    
class Vehicle 
{    
public:    
     Vehicle(float speed,int total) 
     { 
         Vehicle::speed = speed; 
         Vehicle::total = total; 
     } 
    virtual void ShowMember()//    
     { 
        cout<<speed<<"|"<<total<<endl; 
     } 
protected:    
    float speed; 
    int total; 
};    
class Car:public Vehicle    
{    
public:    
     Car(int aird,float speed,int total):Vehicle(speed,total)    
     {    
         Car::aird = aird;    
     } 
    virtual void ShowMember()//   ,     ,       ,   virtual      
     { 
        cout<<speed<<"|"<<total<<"|"<<aird<<endl; 
     } 
public:    
    int aird; 
}; 

void test(Vehicle &temp) 
{ 
     temp.ShowMember(); 
} 

int main()    
{    
     Vehicle a(120,4); 
     Car b(180,110,4); 
     test(a); 
     test(b); 
    cin.get(); 
}

マルチステート特性の動作は虚関数の定義に依存し,マルチステート問題を解決するリロードメンバ関数を必要とする前にvirtualキーワードを加えると,このメンバ関数は虚関数となり,前例のコード実行の結果から,システムはオブジェクトの真のクラス型を分解することに成功し,それぞれのリロードメンバ関数の呼び出しに成功した.
マルチステート特性はプログラマに細部の考慮を省き、開発効率を高め、コードを大幅に簡略化させ、もちろん虚関数の定義にも欠陥があり、マルチステート特性はデータストレージと実行命令のオーバーヘッドを増加させるため、マルチステートを使用しないほうがよい.
 
虚関数の定義は、次の重要なルールに従います.1.虚関数がベースクラスと派生クラスに現れ、名前が同じで形式パラメータが異なるか、戻りタイプが異なる場合、virtualキーワードを付けてもヒステリシス結合は行われません.  2.クラスのメンバー関数のみが虚関数として説明できます.虚関数は継承関係のあるクラスオブジェクトにのみ適用されるため、通常の関数は虚関数として説明できません.  3.静的メンバー関数は、オブジェクトに制限されないことを特徴とするため、虚関数ではありません.  4.インライン関数は、インライン関数が実行中に位置を動的に決定できないため、虚関数ではありません.虚関数がクラスの内部で定義されていても、コンパイル時にシステムは非インラインと見なします.  5.コンストラクション関数は虚関数ではありません.コンストラクションの場合、オブジェクトはまだビット型の空間なので、コンストラクションが完了した後、オブジェクトだけが具体的なクラスのインスタンスです.  6.構造関数は虚関数であってもよく、通常は虚関数と呼ばれます.説明すると、虚関数を使用すると効率が低下すると言われていますが、プロセッサの速度がますます速くなる今日、1つのクラスのすべてのメンバー関数をvirtualと定義することは常にメリットがあります.それは、追加のオーバーヘッドを増加させる以外に、クラスのパッケージ特性を保証するのにメリットがあります.
上の虚関数で使用される重要なルール6について,多態特性を持つクラスの構造関数がvirtualとして宣言される必要がある理由を実例で説明する必要がある.コードは次のとおりです.
#include <iostream>      
using namespace std;    
    
class Vehicle 
{    
public:   
     Vehicle(float speed,int total) 
     { 
         Vehicle::speed=speed; 
         Vehicle::total=total; 
     } 
    virtual void ShowMember() 
     { 
        cout<<speed<<"|"<<total<<endl; 
     } 
    virtual ~Vehicle() 
     { 
        cout<<"  Vehicle      "<<endl; 
        cin.get(); 
     } 
protected:    
    float speed; 
    int total; 
};    
class Car:public Vehicle    
{    
public:    
     Car(int aird,float speed,int total):Vehicle(speed,total)    
     {    
         Car::aird=aird;    
     } 
    virtual void ShowMember() 
     { 
        cout<<speed<<"|"<<total<<"|"<<aird<<endl; 
     } 
    virtual ~Car() 
     { 
        cout<<"  Car       "<<endl; 
        cin.get(); 
     } 
protected:    
    int aird; 
};    

void test(Vehicle &temp) 
{ 
     temp.ShowMember(); 
} 
void DelPN(Vehicle *temp) 
{ 
    delete temp; 
} 
void main() 
{    
     Car *a=new Car(100,1,1); 
     a->ShowMember(); 
     DelPN(a); 
    cin.get(); 
}

上記の例のコードの実行結果から、DelPN(a)が呼び出されると、その後,構造を解析する際には,まずCarクラスの構造関数を呼び出すことに成功したが,構造関数のvirtual修飾を除いて結果を観察すると,構造を解析する際には常にベースクラスの構造関数のみを呼び出すことに成功し,多態の特異的なvirtual修飾は,ベースクラスや派生クラスの一般メンバー関数だけに必要ではないことが分かった.また,ベースクラスと派生クラスの構造関数も同様に重要である.