[英]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
除非你確定自己在做什么,否則不應該嘗試訪問vtable指針。 就語言而言,通常是我們用來定義程序含義的語言,甚至不存在vtable。 它是一個實現細節,它屬於實現(也就是編譯器和運行時環境)。
如果實現受到便攜式ABI(應用程序二進制接口)的約束,那么ABI將說明在哪里找到vtable指針以及vtable內部的內容。 reinterpret_cast< vtable const * const & >( my_obj )
應該可以在任何“合理的”ABI上獲取來自對象的指針。
這樣的程序將被限制在一個平台和一個ABI上。 (C ++ ABI接口往往比C更頻繁地更改,但比其他語言更少。)取決於ABI是一個糟糕的設計選擇,除非你只是想證明你瘋了。
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.