關於c++多態

  推薦《Inside The c++ Object Model 》, 文章轉自其中一段。c++

  多態是面向對象的一個重要特徵,c++中多態是經過虛函數機制實現的,關於c++多態實現的一些基本知識,本文就不在細述。ide

  一般相似這樣:函數

     Shape * ps = new circle;佈局

     ps->Rotate();  //調用的是虛函數設計

    雖然ps是shape類型指針, 可是調用的是circle中的Rotate方法。這是毫無疑問的, 這樣作會很易於咱們程序的封裝。多態的主要用途是經由一個共同的接口來影響類型的封裝, 這個接口一般被定義在一個抽象的base class中。一個指針, 無論指向哪種類型數據在咱們的機器上他自己所須要的內存大小是固定的,如16位機器上是2byte,32位機器上是4byte,咱們使用指針能夠調用對象函數、成員, 是由於咱們知道這個類型對象在內存中所佔的區域大小, 經過指針天然能找到其中的成員地址以及虛函數列表指針。舉個列子,下面有個ZooAnimal聲明:指針

  class ZooAnimal {對象

  public:blog

    ZooAnimal();繼承

    virtual ~ZooAnimal();接口

    //...

    virtual void rotate();

  protected:

    int loc;

    String   name;

  };

 

  ZooAnimal  za("Zoey");

  ZooAnimal  *pza = & za;

其中class object za 和指針pza的可能佈局如圖下所示:

  

 

可是, 一個指向ZooAnimal的指針是如何的與一個指向整數的指針或template Array(以下, 與一個String一併產生)指針有所不一樣呢?

  ZooAnimal * px;

  int *pi;

  Array<String> * pta;

之內存需求的觀點來講, 沒有什麼不一樣!他們三個都須要足夠的內存來存放一個機器地址(32位爲4個bytes)。「指向不一樣類型之個指針」間的差別,既不在其指針表示法不一樣,也再也不其內容(表明一個地址)不一樣,而是在其所尋址出來的Object類型不一樣。也就是說,「指針類型」會告訴編譯器如何解釋某個特定地址中的內存內容及其大小:

  嗯, 那麼, 一個指向地址1000而類型爲void*的指針,將涵蓋怎樣的地址空間?是的, 咱們不知道!這就是爲何一個類型爲void的指針只可以含有一個地址,而不能經過它操做所指的object的緣故。

  因此,轉型(cast)實際上是一種編譯器指令,大部分狀況先他並不改變一個指針所含的真正地址, 它隻影響「被指出以內存的大小和其內容」的解釋方式。

    如今,讓咱們定義一個Bear, 做爲一種ZooAnimal。固然,經由「public繼承」能夠完成這件任務:

             class Bear:public ZooAnimal{

      public :

        Bear();

        ~Bear();

        //..

        void rotate();

        virtual void dance();

      protected:

        enum Dances{...};

        Dances dances_known;

        int cell_block;

    }

    Bear  b("Yogi");

    Bear *pb = &b;

    Bear &rb = *pb;

b、pb、rb會有怎樣的內存需求呢?不論是pointer或者reference都只須要4個bytes(16位上2-bytes)空間。Bear Object須要24bytes, 也就是ZooAnimal的16bytes加上Bear所帶來的8bytes,,如圖下展現了可能的內存佈局:

    

  好, 假設,咱們的Bear Object放在地址的1000處, 一個Bear指針和一個ZooAnimal指針有什麼不一樣?

      Bear b;

      ZooAnimal *pz = &b;

      Bear * pb = &b;

  它們每一個都指向Bear Object的第一個byte。 其間的差異, pb所涵蓋的地址包含整個Bear object,而pz所涵蓋的地址只包含Bear Object中ZooAnimal subObject 。

  除了ZooAnimal subObject中出現的members, 你不能狗使用pz來直接處理Bear的任何members。惟一的列外是經過virtual機制:

    //不合法:cell_block不是ZooAnimal的一個member

    //雖然咱們知道pz當前指向衣蛾Bear Object。

    pz->cell_block;    

    //ok: 通過一個明白的downcast操做就沒問題

    ((Bear*)pz)->cell_block;

    //下面這樣更好, 但它是一個run-time operation(成本較高)

    if (Bear* pb2 = dynamic_cast<Bear*>(pz))

      pb2->cell_block;

    //Ok, 由於cell_block是Bear的一個member

    pb->cell_block;

  當咱們寫:

    pz->rotate();

時,pz的類型將在編譯時期決定如下的兩點

    1) 固定的可用接口。也就是說,pz只可以調用ZooAnimal的public接口

    2) 該接口的access level(列如rotate()是ZooAnimal的一個public member)

  在每個執行點, pz所指的object類型能夠決定rotate()所調用的實體。類型信息的封裝並非維護於pz之中,而是維護與link之中,此link存在於「Object的vptr」 和「vitual table」之間。

  如今, 請看看這種狀況:

      Bear b;

      ZooAnima za = b; //譯註:這裏會引發切割(sliced)

      //調用ZooAnimal::rotate()

      za.rotate(); 

  爲何rotate()所調用的是ZooAnimal實體而不是Bear實體? 此外,若是初始化函數(譯註:應用與行數assignment操做發生時)將一個object內容完整拷貝到另外一個object中去, 爲何za的vtr不指向Bear的virtual table?

  第二個問題的答案是,編譯器在(1)初始化(2)指定(assignment)操做(將一個class object指定給另外一個class object)之間作出了仲裁。編譯器必須確保若是某個Object含有一個或一個以上的vptrs,那寫vptrs的內容不會被base class object初始化或改變。

  至於第一個問題的答案是:za並非(並且也毫不會是)一個Bear,它是(而且只能是)一個ZooAnimal。多態所形成的「一個以上的類型」的潛在力量,並不能實際發揮在「直接存取objects」這件事上。有一個似是而非的觀念:OO程序設計並不支持對Object的直接處理。舉個例子,下面這一組定義:

    {    

      //注:Panda繼承Bear ,Bear繼承ZooAnimal

      ZooAnimal za;

      ZooAnimal *pza;

      

      Bear b;

      Panda *pp =new Panda;

      pza = &b;

    }

  其可能的內存佈局以下圖:

 

  將za或b的地址,或pp所含的內容(也是個地址)指定給pza, 顯然不是問題。一個Pointer或一個reference之因此支持多臺,是由於他們並不引起內存中任何「與類型有關的內存委託操做(type-dependent commitment)」;會受到改變的只是它們所指向的內存的「大小和內容解釋方式」而已。

  然而,任何人若是改變Object za的大小(或是被指定爲)一個derived class Object時, derived object就會被切割, 以塞入較小的base type內存中, derived type將沒有留下任何蛛絲馬跡。多態因而再也不呈現,而一個嚴格的編譯器能夠再編譯時期解析一個「經過該Object而觸發的virtual function調用操做」,於是迴避virtual機制。若是virtual function 被定義俄日inline,則更有效率上的大收穫。

   總而言之,多態是一種威力強大的設計機制,容許你繼承一個抽象的public接口以後,封裝相關的類型。然而須要付出的代價就是額外的間接性--不管是在「內存的得到」或是「類型的決斷」上。c++經過class 的pointer和references 來支持多態,這種程序設計風格就是「面向對象」。

相關文章
相關標籤/搜索