作品C ++の仮想関数

静的結合と動的結合

静的結合と動的バインディングを議論、理解するあなた最初の必要性は、結合されたものを、結合されましたか?機能と関数自体を呼び出す関連して、メンバー間の関係は、変数のメモリアドレスへのアクセスを、結合と呼ばれます。私たちは理解して、静的および動的バインディング理解されます。

  • 静的バインディング:プログラム、コードの所望の組み合わせ、静的結合を呼び出す手順に応じて、関数呼出しをコンパイルするプロセスを指します。これは、コンパイル時に発生します。
  • 動的バインディング:実際のタイプに適切なメソッドを呼び出し参照されたオブジェクトの実際のタイプ、実行中に判定手段。プログラムの実行、関数呼び出しと呼び出し所望の組み合わせに応じて、手順コードの間の動的バインディングと呼ばれています。これは、実行中に発生しました。

Cに結合ダイナミック++

C ++動的バインディングは、特に多状態の実装形態では、仮想関数を介して達成されます。仮想関数を仮想関数テーブルによって達成されます。テーブルには、動的バインディングオブジェクトの実際の型の正しい関数を呼び出すことができます保証の対象相続の問題を解決し、仮想関数のアドレスを記録します。代わりにこの仮想関数テーブル?標準C ++仕様が入ってくる、コンパイラは、vtableのポインタ(仮想関数への正しいオフセットを保証するために取られる)最前位置にオブジェクト・インスタンスに存在することを確認する必要がありつまり、我々は、関数ポインタを横断し、対応する関数を呼び出すことができ、オブジェクトのインスタンスの仮想関数テーブルを介してこのアドレスを取得することができます。

これは、仮想関数の作品

動的バインディングを理解するには、仮想関数の動作原理を理解することが必要です。C ++仮想関数に実装典型的には、(C ++仕様は、使用される特定のどの方法を指定しないが、コンパイラベンダーのほとんどはこの方法を選択した)仮想関数テーブルにより実現されます。クラス仮想関数テーブルは、一つの連続メモリは、各メモリセルに記録されたJMP命令アドレスです。コンパイラは、各クラスの仮想関数テーブルは、仮想機能を有し作成し、仮想関数テーブルは、そのクラスのすべてのオブジェクトによって共有されます。各メンバーのクラスの仮想関数テーブルは、仮想回線を占有しました。Nクラスの仮想関数がある場合は、仮想関数テーブルのサイズはN * 4バイトを持つことになります。

仮想関数(仮想)が仮想関数テーブルの手段によって達成されるメインテーブルは、クラスの仮想関数のアドレスであり、このテーブルでは、この表には問題を覆う、連続を解決し、その実際の機能の真の反射を確保します。したがって、割り当てられたメモリのテーブルに仮想関数ポインタを持つクラスのインスタンスでは、(最前オブジェクトインスタンスに位置する)、そのようにするときの動作のサブクラスに親クラスのポインタ場合、この仮想関数テーブル呼び出される実際の関数を示すことが重要です。どのようにそれを示しているのでしょうか?これは、後述します。

JMP命令は無条件分岐命令のアセンブラ言語、無条件ジャンプ命令は、メモリのいずれかのセクションに移動することです。転送命令アドレスで指定することができるレジスタで与えられ、又はリザーバ内に示されてもよいです。

まず、仮想関数で基本クラスを定義します

class Base
{
public:
    virtual void fun1(){
        cout<<"base fun1!\n";
    }
    virtual void fun2(){
        cout<<"base fun2!\n";
    }
    virtual void fun3(){
        cout<<"base fun3!\n";
    }

    int a;
};

メモリレイアウト表示
ここで説明する絵を書きます
ベースのものメンバー変数に続く、我々は最初の位置に基本クラス、ストレージ仮想関数テーブルポインタのメモリレイアウトで見ることができるが、。さらに、仮想関数テーブル、すべての仮想関数の基本クラスのテーブル記憶があります。

仮想関数テーブルポインタは、通常、オブジェクトのインスタンスの位置の前方に配置されているので、我々は次のコードを、コードを介して仮想テーブルのより良い理解を仮想関数テーブルにアクセスすることができなければなりません。

#include "stdafx.h"
#include<iostream>
using namespace std;

class Base
{
public:
    virtual void fun1(){
        cout<<"base fun1!\n";
    }
    virtual void fun2(){
        cout<<"base fun2!\n";
    }
    virtual void fun3(){
        cout<<"base fun3!\n";
    }

    int a;
};

int _tmain(int argc, _TCHAR* argv[])
{
    typedef void(*pFunc)(void);
    Base b;
    cout<<"虚函数表指针地址:"<<(int*)(&b)<<endl;

    //对象最前面是指向虚函数表的指针,虚函数表中存放的是虚函数的地址
    pFunc pfun;
    pfun=(pFunc)*((int*)(*(int*)(&b)));  //这里存放的都是地址,所以才一层又一层的指针
    pfun();
    pfun=(pFunc)*((int*)(*(int*)(&b))+1);
    pfun();
    pfun=(pFunc)*((int*)(*(int*)(&b))+2);
    pfun();

    system("pause");
    return 0;
}

結果:
ここで説明する絵を書きます

この例では、仮想関数テーブルポインタにより、仮想関数テーブルには、十分に理解しています。ここで再び深いです。C ++は、それの多型を達成するために、基本クラスのポインタと仮想関数を使用する方法ですか?ここでは、環境に継承される仮想関数テーブルで作業する方法を見つけ出す必要があります。仮想継承、多重継承は、後で理解されるように、現在だけで、単一継承を理解しています。
単一継承のコードは次のとおりです。

class Base
{
public:
    virtual void fun1(){
        cout<<"base fun1!\n";
    }
    virtual void fun2(){
        cout<<"base fun2!\n";
    }
    virtual void fun3(){
        cout<<"base fun3!\n";
    }

    int a;
};

class Child:public Base
{
public:
    void fun1(){
        cout<<"Child fun1\n";
    }
    void fun2(){
        cout<<"Child fun2\n";
    }
    virtual void fun4(){
        cout<<"Child fun4\n";
    }
};

メモリレイアウト比較:
ここで説明する絵を書きます
ここで説明する絵を書きます
これとは対照的に、我々が見ることができます:

  • 単一継承では、クラスは、対応する位置が新たな機能の子クラス仮想関数テーブルにより置換され、そして機能が変更されない覆われていないとして具現同じ名前の基本子クラスの仮想関数をオーバーライド。
  • サブクラスは、仮想関数を所有するために、直接、仮想関数テーブルに追加。

さらに、我々は基本クラスの子クラスはポインタのみのvfptrを持っていることに注意してください、私たちは、以前の仮想関数テーブルへのポインタを言って、私たちの出力クラスのvfptrベースの子クラス:

int _tmain(int argc, _TCHAR* argv[])
{
    typedef void(*pFunc)(void);
    Base b;
    Child c;
    cout<<"Base类的虚函数表指针地址:"<<(int*)(&b)<<endl;
    cout<<"Child类的虚函数表指针地址:"<<(int*)(&c)<<endl;

    system("pause");
    return 0;
}

結果:
ここで説明する絵を書きます

可以看到,类Child和类Base分别拥有自己的虚函数表指针vfptr和虚函数表vftable。

下面这段代码,说明了父类和基类拥有不同的虚函数表,同一个类拥有相同的虚函数表,同一个类的不同对象的地址(存放虚函数表指针的地址)不同。

int _tmain(int argc, _TCHAR* argv[])
{
    Base b;
    Child c1,c2;
    cout<<"Base类的虚函数表的地址:"<<(int*)(*(int*)(&b))<<endl;
    cout<<"Child类c1的虚函数表的地址:"<<(int*)(*(int*)(&c1))<<endl;  //虚函数表指针指向的地址值
    cout<<"Child类c2的虚函数表的地址:"<<(int*)(*(int*)(&c2))<<endl;

    system("pause");
    return 0;
}

运行结果:
ここで説明する絵を書きます

在定义该派生类对象时,先调用其基类的构造函数,然后再初始化vfptr,最后再调用派生类的构造函数( 从二进制的视野来看,所谓基类子类是一个大结构体,其中this指针开头的四个字节存放虚函数表头指针。执行子类的构造函数的时候,首先调用基类构造函数,this指针作为参数,在基类构造函数中填入基类的vfptr,然后回到子类的构造函数,填入子类的vfptr,覆盖基类填入的vfptr。如此以来完成vfptr的初始化)。也就是说,vfptr指向vftable发生在构造函数期间完成的。

动态绑定例子:

#include "stdafx.h"
#include<iostream>
using namespace std;

class Base
{
public:
    virtual void fun1(){
        cout<<"base fun1!\n";
    }
    virtual void fun2(){
        cout<<"base fun2!\n";
    }
    virtual void fun3(){
        cout<<"base fun3!\n";
    }

    int a;
};

class Child:public Base
{
public:
    void fun1(){
        cout<<"Child fun1\n";
    }
    void fun2(){
        cout<<"Child fun2\n";
    }
    virtual void fun4(){
        cout<<"Child fun4\n";
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    Base* p=new Child;
    p->fun1();
    p->fun2();
    p->fun3();

    system("pause");
    return 0;
}

运行结果:
ここで説明する絵を書きます
结合上面的内存布局:
ここで説明する絵を書きます

其实,在new Child时构造了一个子类的对象,子类对象按上面所讲,在构造函数期间完成虚函数表指针vfptr指向Child类的虚函数表,将这个对象的地址赋值给了Base类型的指针p,当调用p->fun1()时,发现是虚函数,调用虚函数指针查找虚函数表中对应虚函数的地址,这里就是&Child::fun1。调用p->fun2()情况相同。调用p->fun3()时,子类并没有重写父类虚函数,但依旧通过调用虚函数指针查找虚函数表,发现对应函数地址是&Base::fun3。所以上面的运行结果如上图所示。

ここでは、基本クラスのサブクラスポイントポインタのインスタンスは、(仮想)関数のサブクラス呼び出すことができる理由を理解する必要がありますか?オブジェクトの各インスタンスがvfptrポインタが存在し、最初の値をvfptr除去するコンパイラは、この値は、この値に応じて仮想関数テーブルvftableのアドレスは、呼び出された関数vftableあります。したがって、限りvfptr異なるvtableのvftable点は、仮想関数テーブルに対応するクラスの仮想関数のアドレス、従って多型を達成する、異なる、および異なる「効果」。

共有するために、バックエンドの開発、ブロックチェーン技術をプッシュし、マイクロチャネル公共数に焦点を当てます!

おすすめ

転載: www.cnblogs.com/s-lisheng/p/11287214.html