C++類物件大小的計算(三)含有虛擬函式、虛繼承類大小計算
在前一篇文章《C++類物件大小的計算(二)含有虛擬函式類大小計算》的基礎上,我們來討論如果包含虛擬函式時,對類物件大小的影響。
以下記憶體測試環境為Win7+VS2012,作業系統為32位
六、當類中含有虛繼承情況時
1. 派生類物件中會新增一個指標,該指標指向虛繼承的基類,稱為虛類指標(cPtr)。每一個指標只指向其中一個虛繼承的類,也就是說,虛繼承了幾個類,就會有幾個cPtr。
2. 父類當中的成員變數、虛擬函式指標(vPtr)、虛類指標(cPtr)仍然會被複制到派生類當中。但在不同繼承模式下,vPtr和cPtr的效果是不同的。
vPtr:普通繼承時,子類當中如果有虛擬函式,會直接在父類的虛擬函式表中新增或者替換相應的選項;虛繼承時,vPtr指向的基類虛表不可以再增加了;如果在派生類新增虛擬函式,分為三種情況:
情況一:虛擬函式名稱與父類當中的某個虛擬函式名相同,且派生類含有建構函式,會在結構體中產生一個和虛基類有關的vtordisp指標,該指標作用暫未知。
情況二:虛擬函式名稱與父類當中的某個虛擬函式名相同,且派生類不含建構函式,會直接修改基類虛擬函式表,類大小不變。
情況三:虛擬函式名稱與父類當中的任何一個虛擬函式都不同,需要重新新增一個vPtr,重新產生一個虛擬函式表,大小就會增加。
cPtr:假設子類D同時繼承了父類B和父類C,兩個父類都虛繼承了類A(A是無任何虛繼承的類),根據子類D對B、C繼承方式的不同,其cPtr的個數也是不同的
(1). 父類B、C都是普通繼承。這種情況下有兩個cPtr,分別是從父類B和父類C中繼承過來的,且指向虛繼承的A。
(2). 父類C是虛繼承,B是普通繼承時(或相反情況)。普通繼承父類B時已經繼承過A(有了一個cPtr),因此在虛繼承父類C時,父類C虛繼承的類A就不會再次繼承,因此不會有第二個cPtr指向A。虛繼承父類C也會產生一個cPtr。因此,此種情況下有兩個cPtr。
(3). 父類B、C都是虛繼承。此時指向類A的cPtr仍然只有一個,另外有兩個cPtr指向父類B、C,所以一共有三個cPtr。
下面以幾個例子來理解一下上面所說內容:
情況一:類B虛繼承類A,類C虛繼承類A,類D普通繼承類B、C,各類中均不包含虛擬函式
#include <iostream>
using namespace std;
class A {
public:
A(int x=0) {
cout<<"A"<<x<<endl;
}
};
class B : virtual public A {
public:
B(int x=0) {
cout<<"B"<<x<<endl;
}
};
class C : virtual public A {
public:
C() {
cout<<"C"<<endl;
}
};
class D : public B, public C {
public:
D() {
cout<<"D"<<endl;
}
};
int main() {
A a;
B b;
C c;
D d;
cout<<"size of a:"<<sizeof(a)<<endl;
cout<<"size of b:"<<sizeof(b)<<endl;
cout<<"size of c:"<<sizeof(c)<<endl;
cout<<"size of d:"<<sizeof(d)<<endl;
return 0;
}
VS類結構圖:
執行結果為:
類B、C虛繼承了類A,因此都擁有一個cPtr(類圖中用vbptr表示),因此大小為4。類D普通繼承了B、C,因此複製了兩者的cPtr(都指向類A),大小為8。
情況二:類B虛繼承類A,類C普通繼承類A,類A、B、C中都包含有虛擬函式
#include <iostream>
using namespace std;
class A {
public:
A(int x=0) {
cout<<"A"<<x<<endl;
}
virtual void printA() {
cout<<"Hello A"<<endl;
}
};
class B :virtual public A {
public:
B(int x=0) {
cout<<"B"<<x<<endl;
}
virtual void printA() {
cout<<"Hello A"<<endl;
}
};
class C : public A {
public:
C() {
cout<<"C"<<endl;
}
virtual void printA() {
cout<<"Hello A"<<endl;
}
};
int main() {
A a;
B b;
C c;
cout<<"size of a:"<<sizeof(a)<<endl;
cout<<"size of b:"<<sizeof(b)<<endl;
cout<<"size of c:"<<sizeof(c)<<endl;
return 0;
}
VS類結構圖:
執行結果為:
類A當中因為有虛擬函式,存在一個vPtr,因此結果為4。類B複製了類A的vPtr和虛擬函式表,產生了指向類A的cPtr;因為是類B是虛繼承了類A,且類B當中又有與類A中同名的虛擬函式,因此根據vPtr情況一所示,也就有了一個新的vtordisp指標;共三個指標,因此大小為12。類C是普通繼承類A,複製了類A的虛擬函式表和vPtr,它的虛擬函式也就新增在了這個虛擬函式表中,因此也只有一個指標,大小為4。
情況三:類A為空類;類B、C都虛繼承了類A;類D普通繼承了類B、C;類E普通繼承了類B,虛繼承了類C;類F虛繼承了類B、C,所有類均沒有虛擬函式
#include <iostream>
using namespace std;
class A {
public:
A(int x=0) {
cout<<"A"<<x<<endl;
}
};
class B :virtual public A {
public:
B(int x=0) {
cout<<"B"<<x<<endl;
}
};
class C :virtual public A {
public:
C() {
cout<<"C"<<endl;
}
};
class D : public C, public B {
public:
D() {
cout<<"D"<<endl;
}
};
class E :virtual public C, public B {
public:
E() {
cout<<"E"<<endl;
}
};
class F :virtual public C, virtual public B {
public:
F() {
cout<<"F"<<endl;
}
};
int main() {
A a;
B b;
C c;
D d;
E e;
F f;
cout<<"size of a:"<<sizeof(a)<<endl;
cout<<"size of b:"<<sizeof(b)<<endl;
cout<<"size of c:"<<sizeof(c)<<endl;
cout<<"size of d:"<<sizeof(d)<<endl;
cout<<"size of e:"<<sizeof(e)<<endl;
cout<<"size of f:"<<sizeof(f)<<endl;
return 0;
}
VS類結構圖:
執行結果為:
類A、B、C的大小不做過多討論;類D直接複製了類B和C當中指向類A的cPtr,因此是兩個指標,大小為8;類E中複製了類B當中指向類A的cPtr,繼承類C時因為虛繼承關係不會再一次繼承類A,只會產生一個指向類C的cPtr,因此只有兩個指標, 大小為8;類F除了類E中的兩個指標外,又產生了指向類B的cPtr,一共三個指標,大小為12。
情況四:情況三包含虛擬函式時
#include <iostream>
using namespace std;
class A {
public:
A(int x=0) {
cout<<"A"<<x<<endl;
}
virtual void printA() {
cout<<"Hello A"<<endl;
}
};
class B :virtual public A {
public:
B(int x=0) {
cout<<"B"<<x<<endl;
}
virtual void printB() {
cout<<"Hello B"<<endl;
}
};
class C :virtual public A {
public:
C() {
cout<<"C"<<endl;
}
virtual void printC() {
cout<<"Hello C"<<endl;
}
};
class D : public C, public B {
public:
D() {
cout<<"D"<<endl;
}
virtual void printD() {
cout<<"Hello D"<<endl;
}
};
class E :virtual public C, public B {
public:
E() {
cout<<"E"<<endl;
}
virtual void printE() {
cout<<"Hello E"<<endl;
}
};
class F :virtual public C, virtual public B {
public:
F() {
cout<<"F"<<endl;
}
virtual void printF() {
cout<<"Hello F"<<endl;
}
};
int main() {
A a;
B b;
C c;
D d;
E e;
F f;
cout<<"size of a:"<<sizeof(a)<<endl;
cout<<"size of b:"<<sizeof(b)<<endl;
cout<<"size of c:"<<sizeof(c)<<endl;
cout<<"size of d:"<<sizeof(d)<<endl;
cout<<"size of e:"<<sizeof(e)<<endl;
cout<<"size of f:"<<sizeof(f)<<endl;
return 0;
}
VS類結構圖:
執行結果為:
類B、C都複製了類A的vPtr,但由於都是虛繼承類A且他們當中的虛擬函式與類A中虛擬函式不同名,因此需要重新產生新的vPtr和虛擬函式表,加上各自的cPtr,都有三個指標,大小為12。類D和類E當中都有普通繼承,因此不需要產生新的vPtr。按照前面分析,類D中包含兩個指向類A的cPtr,三個vPtr(分別指向類A、類B、類C的,類D虛擬函式放在了類B或類C的vPtr指向的虛擬函式表中);類E中包含cPtr,分別指向類A、類C,三個vPtr(分別為類A、類B、類C的,類E的虛擬函式放在了類B的vPtr指向的虛擬函式表中);類F中包括三個cPtr(分別指向類A,類B,類C),四個vPtr(分別為類A,類B,類C,還有因為類F中虛擬函式而新建立的),共七個指標,大小為28。 至此,所有關於windows下C++類大小的分析已經全部寫完,需要特別注意的是,以上所有結果都是在用微軟的C++編譯器得到的,經實際測試,相同程式碼在Linux系統下用g++編譯過後會得到完全不同的結果。因此,微軟C++編譯器對類的處理並不是完全按照C++標準來的。 由於C++是一門很複雜的語言,其中的規則很多,細節也很多。這三篇部落格中寫的內容定有不妥之處,歡迎各位指正。