簡體   English   中英

理解虛函數有困難

[英]Trouble with understanding virtual functions

#include<iostream>

using namespace std;

class X
{
    int a;
    int b;
    public:
    void f(int a)
    {
        cout<<"\nInside X";
    }


    virtual void abc ()
    {
        cout<<"\nHello X";
    }

};


class Y : public X
{
    int a;
    public:

    void f(int a, int b)
    {
        cout<<"\nInside Y";
    }


    void abc()
    {
        cout<<"\nHello Y";
    }

};



int main()
{

    X a;

    cout<<sizeof(X);

    Y b;

    cout<<sizeof(Y);

    X *h = new Y;

    h->abc();
}

我理解類X的大小是12個字節的原因是因為它包含一個到虛擬表的vptr(虛擬指針)。 無論如何我可以讀取這個虛擬表,如果沒有,至少可以訪問虛擬指針。 我嘗試過使用工會,但它給了我一些錯誤。

另外,當我調用h-> abc()時,它如何知道類的對象, h指向? 我認為大部分都是在編譯時完成的。 但是當你有一個指向派生類的基類指針時,它如何知道要執行哪個類函數。 考慮這兩種情況

X *h = new X;
h->abc();/* This would call the abc function in X */

X *h = new Y;
h->abc();/* This would call the abc function in Y*/

我讀過, h指針會轉到它指向的對象的vtable,因此會調用該函數嗎? 但是如何在運行時實現?

好的,第一個問題:我舉一個可能更好理解的例子! 在此輸入圖像描述

#include<iostream>
using namespace std;
class Base1 {

    public:

        int ibase1;

        Base1():ibase1(10) {}

        virtual void f() { cout << "Base1::f()" << endl; }

        virtual void g() { cout << "Base1::g()" << endl; }

        virtual void h() { cout << "Base1::h()" << endl; }
};

class Base2 {

    public:

        int ibase2;

        Base2():ibase2(20) {}

        virtual void f() { cout << "Base2::f()" << endl; }

        virtual void g() { cout << "Base2::g()" << endl; }

        virtual void h() { cout << "Base2::h()" << endl; }

};

class Base3 {

    public:

        int ibase3;

        Base3():ibase3(30) {}

        virtual void f() { cout << "Base3::f()" << endl; }

        virtual void g() { cout << "Base3::g()" << endl; }

        virtual void h() { cout << "Base3::h()" << endl; }
};

class Derive : public Base1, public Base2, public Base3 {

    public:

        int iderive;

        Derive():iderive(100) {}

        virtual void f() { cout << "Derive::f()" << endl; }

        virtual void g1() { cout << "Derive::g1()" << endl; }

};

這是一個派生類的內存等級,它實現了三個基類base1,base2,base3,你在其中:

Base1 *p1 = new Derive();
Base2 *p2 = new Derive();
Base3 *p3 = new Derive();

p1將指向vtale1,p2將指向vtable2,p3將指向vtable3,如果調用某個虛函數,它將找到非常虛擬的表並獲取地址!

在你的代碼中:

X *h = new Y;

h將指向Y的內存的開始位置,這是X的虛擬表,他將找到在Y中實現的abc()的地址!

你的第二個問題:

編譯器將成員函數視為普通函數,因此它將成員函數的地址放在code section ,因此它不占用內存!

如果你想閱讀虛擬表,你可以嘗試這樣:我在gcc4.7的例子中試過

typedef void(*Func)(void);
    Derive d;
    int **pd = (int **)(&d);
    int i = 0;
    while(i < 4)
    {
        Func f = (Func)pd[0][i];
        f();
        i++;
    }
    int s = (int)(pd[1]);
    cout << s << endl;
    i = 0;
    cout << "===============================================" << endl;
    while(i < 3)
    {
        Func f = (Func)pd[2][i];
        f();
        i++;
    }
    s = (int)(pd[3]);
    cout << s << endl;
    cout << "===============================================" << endl;
    i = 0;
    while(i < 3)
    {
        Func f = (Func)pd[4][i];
        f();
        i++;
    }
    s = (int)(pd[5]);
    cout << s << endl;
    s = (int)(pd[6]);
cout << s << endl;

你會得到如下結果:

 Derive::f()
Base1::g()
Base1::h()
Derive::g1()
10
===============================================
Derive::f()
Base2::g()
Base2::h()
20
===============================================
Derive::f()
Base3::g()
Base3::h()
30
100
  1. 除非你確定自己在做什么,否則不應該嘗試訪問vtable指針。 就語言而言,通常是我們用來定義程序含義的語言,甚至不存在vtable。 它是一個實現細節,它屬於實現(也就是編譯器和運行時環境)。

    如果實現受到便攜式ABI(應用程序二進制接口)的約束,那么ABI將說明在哪里找到vtable指針以及vtable內部的內容。 reinterpret_cast< vtable const * const & >( my_obj )應該可以在任何“合理的”ABI上獲取來自對象的指針。

    這樣的程序將被限制在一個平台和一個ABI上。 (C ++ ABI接口往往比C更頻繁地更改,但比其他語言更少。)取決於ABI是一個糟糕的設計選擇,除非你只是想證明你瘋了。

  2. vtable標識派生類 - 這是它的目的。 它包含指向由派生類覆蓋基類實現的函數的指針。 它還包含一個結構,其中包含派生類的名稱以及指向其基礎的鏈接,以便動態確定從中派生的內容。

    dynamic_cast用於確定派生和查找派生對象的算法實際上可能非常慢 - 而不是O(1)。 它通常必須搜索基類的鏈接結構。

無論如何我可以閱讀這個虛擬表

不是真的,不知道指針相對於對象指針值的位置,這是編譯器特定的。

如果沒有,至少可以訪問虛擬指針。

為什么? 您可以通過h->abc獲取該功能的地址,這是您想要的嗎?

另外,當我調用h-> abc()時,它如何知道類的對象,h指向?

事實並非如此,它只知道該類的vtable在哪里。 如果使用RTTI,則vtable中有信息告訴它什么類,但調用虛函數不是必需的。 從X派生的每個類都有自己的vtable,包含自己的虛函數指針。 (當然,總是假設基於vtable的實現。)

我讀過,h指針會轉到它指向的對象的vtable,因此會調用該函數嗎? 但是如何在運行時實現?

你剛才自己描述過。 為了稍微詳細說明,指針h->abc解析為h->_vtable[x]表示某個常數x表示abc的虛函數指針的vtable中的偏移量。 所以調用解析為*(h->_vtable[abc])(...)

另一個問題,與我需要清理的虛擬功能無關。 如果函數具有與任何其他變量一樣的地址,為什么它們不占用對象中的空間?

為什么他們呢? 這意味着每個對象中的每個函數的副本。 重點是什么?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM