C++ 函數隱藏(函數名相同才會出現)

來源:互聯網
上載者:User

     看了 林銳 的  《高品質編程指南》8.2.2 令人迷惑的隱藏規則.  (這裡的隱藏是指衍生類別的函數屏蔽了與其同名的基類函數)

 

     這一節寫得很好: 1. 把出現隱藏的情況列舉出來了.

                     2. 舉的例子很貼切, 讓人能更好的理解. 

                     3. 對出現隱藏函數情況的理解.

                     4. 提出對應的解決方案. 

  •  如果衍生類別的函數與基類的函數同名, 但是參數不同. 此時, 不論有無 virtual 關鍵字, 基類的函數將被隱藏(注意別與重載混淆).
  •  如果衍生類別的函數與基類的函數同名, 並且參數也相同, 但是基類函數沒有 virtual 關鍵字. 此時, 基類的函數被隱藏(注意別與覆蓋混淆). 

      就是以上兩種情況導致了函數隱藏的情況出現. 看看書裡的例子:

 

#include <iostream>
using namespace std;
class Base
{
    public:
    virtual void f(float x){cout << "Base::f(float) "  << x << endl;}
            void g(float x){cout << "Base::g(float) "  << x << endl;}
            void h(float x){cout << "Base::h(float) "  << x << endl;}
};

 

class Derived : public Base
{
    public:
    virtual void f(float x){cout << "Derived::f(float) "  << x << endl;}
            void g(int x)  {cout << "Derived::g(int) "  << x << endl;}
            void h(float x){cout << "Derived::h(float) "  << x << endl;}   
};

 

int main()
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;
   
    //沒出現隱藏的情況
    pb->f(3.14f);             //Derived::f(float) 3.14
    pd->f(3.14f);             //Derived::f(float) 3.14
   
    //出現隱藏的情況 1
    pb->g(3.14f);             //Base::g(float) 3.14
    pd->g(3.14f);             //Derived::g(int)  3       (surprise!)
   
    //出現隱藏的情況 2
    pb->h(3.14f);             //Base::h(float) 3.14         (surprise!)
    pd->h(3.14f);             //Derived::h(float)  3.14

    system("pause");
    return 0;
}

 

  •  如果衍生類別的函數與基類的函數同名, 並且參數也相同, 但是基類函數沒有 virtual 關鍵字. 此時, 基類的函數被隱藏(注意別與覆蓋混淆). 

          //出現隱藏的情況
          pb->h(3.14f);             //Base::h(float) 3.14         (surprise!)
          pd->h(3.14f);             //Derived::h(float)  3.14

 

個人看法:

 

     如果你學過 java 的多態, 對這個結果應該很難接受.

 

     Derived 對象d 被隱式轉換為 Base 對象, 那麼該 Base 對象跟Derived 對象d 同名的函數被 Derived 對象d 覆蓋. 所以兩者的執行結果應該是一樣的.

 

     但是這裡是 C++, 不是 java. 對於C++ 來說, 如果 Base 類的某個函數沒有 virtual 關鍵字, 那該函數跟 Derived 類的同名函數(參數也相同)是沒有什麼關係的. 

 

     這個請看下 《C++ Primer》501頁下面的"關鍵概念: 名字尋找和繼承".

 

     pb 是 Base 類指標,  pb指標 綁定到 Derived 對象 d,  但是由於 Base 類的 h(float) 函數不是虛函數,  無論實際對象是什麼類型, 都執行 Base::h(float).  

 

      程式會直接在 Base 類中尋找 h 函數; 如果沒有 h 函數, 那就會去其父類中尋找 h 函數 ; 如果還是找不到 h 函數 , 那就會去其父類的上一層類中繼續尋找 h 函數 ; 一次類推, 一直到找到方法A 為止; 如果最終都找不到, 你的程式應該是不能通過編譯的!(這種尋找方式倒是跟 java 一樣)

 

      java 的函數是沒有 virtual 關鍵字的, 但是衍生類別和基類只要函數名和參數相同, 那麼該函數就被覆蓋了. 如果反過來想, 相對於 C++, 那不是 java 的每個函數都是虛函數嗎?  可能C++ 在於效率上考慮, 不想所有的函數都使用動態聯編.

 

  •  如果衍生類別的函數與基類的函數同名, 但是參數不同. 此時, 不論有無 virtual 關鍵字, 基類的函數將被隱藏(注意別與重載混淆).

          //出現隱藏的情況 1
         pb->g(3.14f);             //Base::g(float) 3.14
         pd->g(3.14f);             //Derived::g(int)  3       (surprise!)

 

個人看法: 

 

  這個其實也不能說是隱藏, 因為 g(float) 和 g(int) 是不同的函數, C++編譯後在符號庫中的名字分別是 _g_float 和 _g_int.即使他們都有 virtual 關鍵字, 但是因為是分別存在與衍生類別和基類中的不同函數, 所以在不存在覆蓋的關係(重載更不可能).

 

     pb 是 Base 類指標,  pb指標 綁定到 Derived 對象 d, Base 類根本就沒有 g(int) 函數, 所以 pd 指標是總不可能去調用 Derived::g(int) 函數的.

 

      pb->g(3.14f);  程式在 Base 類中找到匹配的函數 Base::g(float) , 然後調用這個函數.

 

      pd->g(3.14f);             //Derived::g(int)  3       (surprise!)

           

      編譯先在 Derived 類中尋找匹配 g(3.14f) 的函數,  他找到了 g(int) , 並且在 Derived 類中只有一個函數匹配. 即使 g(int) 是 virtual 的, 但pd 指標指向的 Derived 對象 d 的 g(int) 函數跟 Derived 類的 g(int) 函數是一樣的, 調用的都是 Derived::g(int) 函數, 所以不存在多態, 也就無需動態聯編了. (需要動態聯編的條件請看《C++ Primer》15.2.4 "virtual 與其他成員函數" 開頭部分, 這裡之所以無需動態聯編, 是因為不滿足動態聯編的第二個條件).

 

      即使 Base 類有匹配的函數virtual g(float x), 但是virtual g(float x) 是存放在 Derived 對象 d 的虛函數表(virtual function table, vtbl, plus 13.4.4) 中的, 如果不進行動態聯編, 程式不會去 vtbl 中尋找對應的函數地址, vtbl 中的函數地址是不會被引用到的, 也就不會被調用了.

   

      所以把 Base 類的 g(float x) 加上 virtual 關鍵字, 結果不會改變; 再把 Derived 類的 g(int)  加上 virtual 關鍵字, 結果也是不變的.

 

       如果 Derived 類添加一個函數 virtual void g(float x){cout << "Derived::g(float) "  << x << endl;}, 把 Base 類的 g(float x) 加上 virtual 關鍵字.

 

       那結果就是

                      pb->g(3.14f);             //Derived::g(float) 3.14
                      pd->g(3.14f);             //Derived::g(float) 3.14  

 

        pb->g(3.14f)

 

        pb 是 Base 類指標,  pb指標 綁定到 Derived 對象 d. 由於 Base 類的 g(float) 函數的 virtual 的, 並且是 Base 類指標調用  g(float) 函數, pb指標綁定的對象 d 的靜態類型是 Derived 類, Derived 類的 g(float) 函數也是 virtual 的, 通常只有在運行程式時才能確定對象的動態類型.  所以編譯器對 虛函數 g(float) 使用動態聯編.

 

         因為 Derived 類提供了虛函數 g(float) 的新定義, 所以在 Derived 對象 d 的虛函數表(vtbl) 中g(float) 函數的地址儲存為 Derived::g(float) 函數的地址. pb 指標調用虛函數 g(float) 時候, 程式到 Derived 對象 d 的虛函數表(vtbl) 中尋找 g(float) 函數的地址, 然後就執行該地址的函數. 所以 pb->g(3.14f)  執行了 Derived::g(float) 函數. 

 

 

 

 

 

 

      說起來, 子類要重載父類的方法, 還真是麻煩呢, 難道要全部方法copy 過來? 其實也不必要呢, 如果是子類對象能隱式轉換父類對象, 但是子類自有的方法, 對於基類對象來說是不存在的, 基類對象當然也不能調用這些方法了. 所以呢, 子類不必要重載父類的方法, 建一個屬於自己的方法還更好!  

 

       virtual 關鍵字, 好像就是在告訴你, 我這個函數可以給衍生類別同名字同參數的函數覆蓋; 純虛函數更是直接告訴衍生類別, 你一定要寫一個同名字同參數的函數覆蓋我,  哈哈!

 

     

重要查考: 《C++ Primer》第480頁 "關鍵概念:C++ 中的多態性".

               《C++ Primer plus》13.4.4 虛擬成員函數和動態聯編.

               《C++ Primer plus》第 449 頁 "虛函數的工作原理".

               《C++ Primer》15.2.4 virtual 與其他成員函數.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.