從零開始學C++之虛函數與多態(一):虛函數表指標、虛解構函式、object slicing與虛函數

來源:互聯網
上載者:User

一、多態

多態性是物件導向程式設計的重要特徵之一。
多態性是指發出同樣的訊息被不同類型的對象接收時有可能導致完全不同的行為。
多態的實現:

函數重載

運算子多載

模板

虛函數

(1)、靜態繫結與動態綁定

靜態繫結

綁定過程出現在編譯階段,在編譯期就已確定要調用的函數。

動態綁定

綁定過程工作在程式運行時執行,在程式運行時才確定將要調用的函數。

二、虛函數

虛函數的概念:在基類中冠以關鍵字 virtual 的成員函數
虛函數的定義:

virtual 函數類型 函數名稱(參數列表);

如果一個函數在基類中被聲明為虛函數,則他在所有衍生類別中都是虛函數

只有通過基類指標或引用調用虛函數才能引發動態綁定
虛函數不能聲明為靜態


(1)、虛函數表指標

虛函數的動態綁定是通過虛函數表來實現的。(虛函數表存放虛函數的函數指標)
包含虛函數的類頭4個位元組存放指向虛函數表的指標

注意:若不是虛函數,一般的函數不會出現在虛函數表,因為不用通過虛函數表指標間接去訪問。


 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
using namespace std;

class Base
{
public:
    virtual void Fun1()
    {
        cout << "Base::Fun1 ..." << endl;
    }

    virtual void Fun2()
    {
        cout << "Base::Fun2 ..." << endl;
    }

    void Fun3() //被Derived繼承後被隱藏
    {
        cout << "Base::Fun3 ..." << endl;
    }
};

class Derived : public Base
{
public:
    /*virtual */
    void Fun1()
    {
        cout << "Derived::Fun1 ..." << endl;
    }

    /*virtual */void Fun2()
    {
        cout << "Derived::Fun2 ..." << endl;
    }

    void Fun3()
    {
        cout << "Derived::Fun3 ..." << endl;
    }
};

int main(void)
{
    Base *p;
    Derived d;

    p = &d;
    p->Fun1();      // Fun1是虛函數,基類指標指向衍生類別對象,調用的是衍生類別對象的虛函數(間接)
    p->Fun2();
    p->Fun3();      // Fun3非虛函數,根據p指標實際類型來調用相應類的成員函數(直接)

    Base &bs = d;
    bs.Fun1();
    bs.Fun2();
    bs.Fun3();

    d.Fun1();
    d.Fun2();
    d.Fun3();

    return 0;
}



sizeof(Base); 和 sizeof(Derived); 都是4個位元組,其實就是虛表指標,據此可以畫出對象的模型:


Derived類繼承了Base類的虛函數Fun1,Fun2, 但又重新實現了,即覆蓋了。程式中通過基類的指標或引用可以通過vptr間接訪問到Derived::Fun1, Derived:::Fun2,但因為Fun3不是虛函數(基類的Fun3 被繼承後被隱藏),故p->Fun3(); 和bs.Fun3(); 根據指標或引用的實際類型去訪問,即訪問到被Derived繼承下來的基類Fun3。函數的覆蓋與隱藏可以參考這裡。


三、虛解構函式

何時需要虛解構函式?
當你可能通過基類指標刪除衍生類別對象時
如果你打算允許其他人通過基類指標調用對象的解構函式(通過delete這樣做是正常的),並且被析構的衍生類別對象是有重要的解構函式需要執行,就需要讓基類的解構函式作為虛函數。


 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
using namespace std;

class Base
{
public:
    virtual void Fun1()
    {
        cout << "Base::Fun1 ..." << endl;
    }

    virtual void Fun2()
    {
        cout << "Base::Fun2 ..." << endl;
    }

    void Fun3()
    {
        cout << "Base::Fun3 ..." << endl;
    }

    Base()
    {
        cout << "Base ..." << endl;
    }
    // 如果一個類要做為多態基類,要將解構函式定義成虛函數
    virtual ~Base()
    {
        cout << "~Base ..." << endl;
    }
};

class Derived : public Base
{
public:
    /*virtual */
    void Fun1()
    {
        cout << "Derived::Fun1 ..." << endl;
    }

    /*virtual */void Fun2()
    {
        cout << "Derived::Fun2 ..." << endl;
    }

    void Fun3()
    {
        cout << "Derived::Fun3 ..." << endl;
    }
    Derived()
    {
        cout << "Derived ..." << endl;
    }
    /*  virtual*/ ~Derived() //即使沒有virtual修飾,也是虛函數
    {
        cout << "~Derived ..." << endl;
    }
};

int main(void)
{
    Base *p;
    p = new Derived;

    p->Fun1();
    delete p; //通過基類指標刪除衍生類別對象

    return 0;
}


即通過delete 基類指標刪除了衍生類別對象(執行衍生類別解構函式),此時就好像delete 衍生類別指標 效果一樣。如果基類解構函式沒有聲明為virtual,

此時只會輸出~Base。


四、object slicing與虛函數

首先看的繼承體系:


 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
using namespace std;

class CObject
{
public:
    virtual void Serialize()
    {
        cout << "CObject::Serialize ..." << endl;
    }
};

class CDocument : public CObject
{
public:
    int data1_;
    void func()
    {
        cout << "CDocument::func ..." << endl;
        Serialize();
    }
    virtual void Serialize()
    {
        cout << "CDocument::Serialize ..." << endl;
    }
    CDocument()
    {
        cout << "CDocument()" << endl;
    }
    ~CDocument()
    {
        cout << "~CDocument()" << endl;
    }
    CDocument(const CDocument &other)
    {
        data1_ = other.data1_;
        cout << "CDocument(const CDocument& other)" << endl;
    }
};

class CMyDoc : public CDocument
{
public:
    int data2_;
    virtual void Serialize()
    {
        cout << "CMyDoc::Serialize ..." << endl;
    }
};

int main(void)
{
    CMyDoc mydoc;
    CMyDoc *pmydoc = new CMyDoc;

    cout << "#1 testing" << endl;
    mydoc.func();

    cout << "#2 testing" << endl;
    ((CDocument *)(&mydoc))->func();

    cout << "#3 testing" << endl;
    pmydoc->func();

    cout << "#4 testing" << endl;
    ((CDocument)mydoc).func();      //mydoc對象強制轉換為CDocument對象,向上轉型
    //完完全全將衍生類別對象轉化為了基類對象
    //包括虛函數表也變成基類的虛表
    delete pmydoc;

    return 0;
}



由於Serialize是虛函數,故前3個testing輸出都是CMyDoc::Serialize ...但第4個testing中發生了Object Slicing,即對象切割,將CMyDoc對象轉換成基類CDocument對象時,調用了CDocument類的拷貝建構函式,CMyDoc類的額外成員如data2_消失,成為完全一個CDocument對象,包括虛表也變成基類的虛表,故輸出的是CDocument::Serialize ...

此外還可以看到,調用了兩次CDocument建構函式和一次CDocument 拷貝建構函式,CDocument解構函式被調用3次。


參考:

C++ primer 第四版
Effective C++ 3rd
C++編程規範

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.