1. 程式人生 > >C++類物件大小的計算(三)含有虛擬函式、虛繼承類大小計算

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++是一門很複雜的語言,其中的規則很多,細節也很多。這三篇部落格中寫的內容定有不妥之處,歡迎各位指正。