1. 程式人生 > >虛擬函式、純虛擬函式、虛繼承

虛擬函式、純虛擬函式、虛繼承

實現多型。
虛擬函式都是動態繫結,繫結的是動態型別,所對應的函式或屬性依賴於物件的動態型別,發生在執行期;

虛擬函式

  • 類裡如果聲明瞭虛擬函式,這個函式是實現的,哪怕是空實現,它的作用就是為了能讓這個函式在它的子類裡面可以被覆蓋,這樣的話,這樣編譯器就可以使用後期繫結來達到多型了。
  • 虛擬函式的類用於 “實作繼承”,繼承介面的同時也繼承了父類的實現。

特性:

  • 普通函式(非類成員函式)不能是虛擬函式
  • 靜態函式(static)不能是虛擬函式
  • 建構函式不能是虛擬函式(因為在呼叫建構函式時,虛表指標並沒有在物件的記憶體空間中,必須要建構函式調- 用完成後才會形成虛表指標)
  • 行內函數不能是表現多型性時的虛擬函式

純虛擬函式

  • 只是一個介面,是個函式的宣告而已。
  • 純虛擬函式關注的是介面的統一性,實現由子類完成。
  • 帶純虛擬函式的類叫虛基類,這種基類不能直接生成物件,而只有被繼承,並重寫其虛擬函式後,才能使用。對於虛基類,是含有純虛擬函式的類,它如果被繼承,那麼子類就必須實現虛基類裡面的所有純虛擬函式,其子類不能是抽象類。

虛解構函式:為了解決基類的指標指向派生類物件,並用基類的指標刪除派生類物件。

class Shape{
public:
    Shape();                    // 建構函式不能是虛擬函式
virtual double calcArea(); virtual ~Shape(); // 虛解構函式 }; class Circle : public Shape{ // 圓形類 public: virtual double calcArea(); ... }; int main(){ Shape * shape1 = new Circle(4.0); shape1->calcArea(); delete shape1; // 因為Shape有虛解構函式,所以delete釋放記憶體時,先呼叫子類解構函式,再呼叫基類解構函式,防止記憶體洩漏。
shape1 = NULL; return 0}

虛擬函式指標和虛擬函式列表

當一個類定義了虛擬函式會隱式的建立一個自己的虛擬函式指標和虛擬函式列表。
虛擬函式指標:在含有虛擬函式類的物件中,指向虛擬函式表,在執行時確定。
虛擬函式表:在程式只讀資料段,存放虛擬函式指標,如果派生類實現了基類的某個虛擬函式,則在虛表中覆蓋原本基類的那個虛擬函式指標,在編譯時根據類的宣告建立。

虛繼承

虛繼承用於解決多繼承條件下的菱形繼承問題(浪費儲存空間、存在二義性)。
菱形繼承問題
虛繼承底層實現原理與編譯器相關,一般通過虛基類指標和虛基類表實現,每個虛繼承的子類都有一個虛基類指標(佔用一個指標的儲存空間,4位元組)和虛基類表(不佔用類物件的儲存空間)(需要強調的是,虛基類依舊會在子類裡面存在拷貝,只是僅僅最多存在一份而已,並不是不在子類裡面了);當虛繼承的子類被當做父類繼承時,虛基類指標也會被繼承。

實際上,vbptr指的是虛基類表指標(virtual base table pointer),該指標指向了一個虛基類表(virtual table),虛表中記錄了虛基類與本類的偏移地址;通過偏移地址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持著公共基類(虛基類)的兩份同樣的拷貝,節省了儲存空間。

#include<iostream>
using namespace std;
 
class A  //大小為4
{
public:
	int a;
};
class B :virtual public A  //大小為12,變數a,b共8位元組,虛基類表指標4
{
public:
	int b;
};
class C :virtual public A //與B一樣12
{
public:
	int c;
};
class D :public B, public C //24,變數a,b,c,d共16,B的虛基類指標4,C的虛基類指標
{
public:
	int d;
};
 
int main()
{
	A a;
	B b;
	C c;
	D d;
	cout << sizeof(a) << endl;//4
	cout << sizeof(b) << endl;//12
	cout << sizeof(c) << endl;//12
	cout << sizeof(d) << endl;//24
	return 0;
}

虛繼承、虛擬函式 關係

  • 相同之處:
    • 都利用了虛指標(均佔用類的儲存空間)和虛表(均不佔用類的儲存空間)
  • 不同之處:
    • 虛繼承
      • 虛基類依舊存在繼承類中,只佔用儲存空間
      • 虛基類表儲存的是虛基類相對直接繼承類的偏移
    • 虛擬函式
      • 虛擬函式不佔用儲存空間
      • 虛擬函式表儲存的是虛擬函式地址

模板類、成員模板、虛擬函式 關係

模板類中可以使用虛擬函式
一個類(無論是普通類還是類模板)的成員模板不能是虛擬函式

成員模板

class D{
public:
	template<class T>
	void ggg(T a) {
		cout << a << endl;
	}
};

D d;
d.ggg<int>(3);