12、虛函數的應用、虛析構函數
- 運行時類型信息(RTTI)
- 動態類型轉換(dynamic_cast)
- typeid操作符
- 虛 析構函數
- 空虛析構函數
一個類中,除了構造函數和靜態成員函數外,任何函數都可以被聲明為虛函數
運行時類型信息(RTTI)
動態類型轉換(dynamic_cast)
- 用于將基類類型的指針或引用轉換為其子類類型的指針或引用
- 前提是子類必須從基類多態繼承 (即基類包含至少一個虛函數)
- 動態類型轉換會對所需轉換的基類指針或引用做檢查,如果其指向的對象的類型與所要轉換的目標類型一致,則轉換成功,否則轉換失敗。
- 針對指針的動態類型轉換,以返回空指針(NULL)表示失敗,針對引用的動態類型轉換以拋出
bad_cast
異常表示失敗
// 動態類型轉換 :基類類型指針轉換為子類類型指針
// 基類類型引用轉換為子類類型引用
#include <iostream>
using namespace std;class A { // 編譯器根據A類信息,將制作一張虛函數表 "A"...|A::foo的地址
public:virtual void foo(){}
};class B : public A { // 編譯器根據B類信息,將制作一張虛函數表 "B"...|A::foo的地址
};class C : public B { // 編譯器根據C類信息,將制作一張虛函數表 "C"...|A::foo的地址
};class D {}; // 編譯器根據D類信息,不制作虛函數表int main( void ){B b; // |虛表指針| --> 編譯器根據B類信息制作的虛函數表A* pa = &b; // B* --> A* (子類類型指針 --> 基類類型指針)cout << "---------------------dynamic_cast 運行期間做的轉換-----------------------" << endl; B* pb = dynamic_cast<B*>(pa); // pa-->b對象所占內存空間-->虛表指針 --> 編譯器根據B類信息制作的虛函數表->"B"cout << "A* pa --> B* pb: " << pb << endl;C* pc = dynamic_cast<C*>(pa); // pa-->b對象所占內存空間-->虛表指針 --> 編譯器根據B類信息制作的虛函數表->"B"cout << "A* pa --> C* pc: " << pc << endl;D* pd = dynamic_cast<D*>(pa); // pa-->b對象所占內存空間-->虛表指針 --> 編譯器根據B類信息制作的虛函數表->"B"cout << "A* pa --> D* pd: " << pd << endl;cout << "---------------------static_cast 編譯期間做的轉換-----------------------" << endl; pb = static_cast<B*>(pa); // 即合理且安全 A* -->B*的反向 可以隱式轉換cout << "A* pa --> B* pb: " << pb << endl;pc = static_cast<C*>(pa); // 有風險 A*-->C*的反向 可以隱式轉換cout << "A* pa --> C* pc: " << pc << endl;// pd = static_cast<D*>(pa); // 不合理 A*-->D*的反向 不可以隱式轉換
// cout << "A* pa --> D* pd: " << pd << endl;return 0;
}
typeid操作符
#include <typeinfo>
- 返回type info類型對象的常引用
- type info類的成員函數name(),返回類型名字符串
- type info類支持“==”和“!=”操作符,可直接用于類型相同與否的判斷
- 當其作用于基類類型的指針或引用的目標對象時
- 若基類不包含虛函數 typeid所返回類型信息由該指針或引用本身的類型決定
- 若基類包含至少一個虛函數,即存在多態繼承,typeid所返回類型信息由該指針或引用的實際目標對象的類型決定
// typeid操作符 -- 獲取對象的類型信息
// 無法獲取對象常屬性信息
#include <iostream>
#include <typeinfo>
using namespace std;class A { // 編譯器根據A類信息,將制作一張虛函數表 "A"...|A::foo的地址 virtual void foo(){}
};class B : public A { // 編譯器根據B類信息,將制作一張虛函數表 "B"...|A::foo的地址
};int main( void ){B b;// |虛表指針| --> 編譯器根據B類信息制作的虛函數表A* pa = &b;A& ra = b;cout << "pa指針的目標對象的類型:" << typeid(*pa).name() << endl;// pa->b對象所占內存空間-->虛表指針-->B類虛函數表-->"B"cout << "ra引用的目標對象的類型:" << typeid(ra).name() << endl;// ra->b對象所占內存空間-->虛表指針-->B類虛函數表-->"B"int m;const type_info& rty = typeid(m);// 1. 獲取m的類型信息(類名、類大小、類版本...)// 2. 創建一個type_info類對象// 3. 將獲取到的m的類型信息保存到type_info對象的私有成員變量中// 4. 返回type_info類對象的常引用string rn = rty.name();cout << "m的類型:" << rn << endl;const int n = 10;cout << "n的類型:" << typeid(n).name() << endl;cout << (typeid(m) == typeid(n)) << endl;cout << (typeid(m)!=typeid(n)) << endl;return 0;
}
虛 析構函數
delete一個基類指針 (指向子類對象)
-
實際被調用的僅僅是基類的析構函數
-
基類的析構函數只負責析構子類對象中的基類子對象
-
基類的析構函數不會調用子類的析構函數
-
在子類中分配的資源將無法得到釋放
-
如果將基類的析構函數聲明為虛函數,那么實際被調用的將是子類的析構函數
-
子類的析構函數將首先釋放子類對象自己的成員,然后再調用基類的析構函數釋放該子類對象的基類部分,最終實現完美的資源釋放
// 虛析構函數 -- delete一個基類類型指針(指向子類對象),能夠正確的調用子類的析構函數
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;class A{
public:A():m_a(open("./file",O_CREAT | O_RDWR, 0644)){//【int m_a = open(..);】定義m_a,初值為文件描述符 -->文件表等內核信息(動態資源)cout << "A()被調用 -- 打開file文件" << endl;}virtual ~A(){ // 虛析構函數close(m_a);cout << "~A()被調用 -- 關閉file文件" << endl;// 釋放m_a本身所占內存空間}
private:int m_a;
};
class B : public A{
public:B():m_b(open("./cfg",O_CREAT | O_RDWR, 0644)){//【A();】定義基類子對象,利用基類子對象.A()//【int m_b = open(...);】定義m_b,初值為文件描述符-->文件表等內核信息(動態資源)cout << "B()被調用 -- 打開cfg文件" << endl;}~B(){ // 虛析構函數close(m_b);cout << "~B()被調用 -- 關閉cfg文件" << endl;// 對于基類子對象,利用基類子對象.~A()// 釋放m_b/基類子對象本身所占內存空間}
private:int m_b;
};int main( void ){A* p = new B; // 定義B堆對象,利用B類堆對象.B()delete p; // p->析構函數(~B()) 釋放B類堆對象本身所占內存空間return 0;
}
空虛析構函數
- 沒有分配任何動態資源的類,無需定義析構函數
- 沒有定義析構函數的類,編譯器會為其提供一個缺省析構函數,但缺省析構函數并不是虛函數
- 為了保證delete一個指向子類對象的基類指針時,能夠正確調用子類的析構函數,就必須把基類的析構函數定義為虛函數,即使它是一個空函數
- 任何時候,為基類定義一個虛析構函數總是無害的
一個類中,除了構造函數和靜態成員函數外,任何函數都可以被聲明為虛函數