C++沒有類似 Java 語言的 instanceof 的類型預斷,有什麼方法可以做到類似 java 的類型預斷功能呢?和Java相比,C++要想獲得運作時類型資訊,隻能通過 RTTI (Run Time Type Identification)機制,并且C++最終生成的代碼是直接與機器相關的(也就是不同編譯器實作RTTI的方式不一樣,C++标準隻是做了約定)。
RTTI 提供了兩個操作符 :typeid 和 dynamic_cast
typeid:傳回指針和引用所指的實際類型
dynamic_cast:将基類類型的指針或引用安全地轉換為其派生類類型的指針或引用
C++的動态多态是由虛函數實作的,對于多态性的對象,無法在程式編譯階段确定對象的類型。當類中含有虛函數時,其基類的指針就可以指向任何派生類的對象,這時就有可能不知道基類指針到底指向的是哪個對象的情況,類型的确定要在運作時利用運作時類型辨別做出。
1、typeid
為了獲得一個對象的類型可以使用 typeid 操作,該操作傳回一個 type_info 類對象的引用,type_info 類的定義在檔案<typeinfo>中。
這裡先介紹一下type_info源碼:
class type_info
{
public:
/** Destructor first. Being the first non-inline virtual function, this
* controls in which translation unit the vtable is emitted. The
* compiler makes use of that information to know where to emit
* the runtime-mandated type_info structures in the new-abi. */
virtual ~type_info();
/** Returns an @e implementation-defined byte string; this is not
* portable between compilers! */
const char* name() const _GLIBCXX_NOEXCEPT
{ return __name[0] == '*' ? __name + 1 : __name; }
#if !__GXX_TYPEINFO_EQUALITY_INLINE
// In old abi, or when weak symbols are not supported, there can
// be multiple instances of a type_info object for one
// type. Uniqueness must use the _name value, not object address.
bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT;
bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT;
#else
#if !__GXX_MERGED_TYPEINFO_NAMES
/** Returns true if @c *this precedes @c __arg in the implementation's
* collation order. */
// Even with the new abi, on systems that support dlopen
// we can run into cases where type_info names aren't merged,
// so we still need to do string comparison.
bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return (__name[0] == '*' && __arg.__name[0] == '*')
? __name < __arg.__name
: __builtin_strcmp (__name, __arg.__name) < 0; }
bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{
return ((__name == __arg.__name)
|| (__name[0] != '*' &&
__builtin_strcmp (__name, __arg.__name) == 0));
}
#else
// On some targets we can rely on type_info's NTBS being unique,
// and therefore address comparisons are sufficient.
bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return __name < __arg.__name; }
bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return __name == __arg.__name; }
#endif
#endif
#if __cpp_impl_three_way_comparison < 201907L
bool operator!=(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return !operator==(__arg); }
#endif
#if __cplusplus >= 201103L
size_t hash_code() const noexcept
{
# if !__GXX_MERGED_TYPEINFO_NAMES
return _Hash_bytes(name(), __builtin_strlen(name()),
static_cast<size_t>(0xc70f6907UL));
# else
return reinterpret_cast<size_t>(__name);
# endif
}
#endif // C++11
// Return true if this is a pointer type of some kind
virtual bool __is_pointer_p() const;
// Return true if this is a function type
virtual bool __is_function_p() const;
// Try and catch a thrown type. Store an adjusted pointer to the
// caught type in THR_OBJ. If THR_TYPE is not a pointer type, then
// THR_OBJ points to the thrown object. If THR_TYPE is a pointer
// type, then THR_OBJ is the pointer itself. OUTER indicates the
// number of outer pointers, and whether they were const
// qualified.
virtual bool __do_catch(const type_info *__thr_type, void **__thr_obj,
unsigned __outer) const;
// Internally used during catch matching
virtual bool __do_upcast(const __cxxabiv1::__class_type_info *__target,
void **__obj_ptr) const;
protected:
const char *__name;
explicit type_info(const char *__n): __name(__n) { }
private:
/// Assigning type_info is not supported.
type_info& operator=(const type_info&);
type_info(const type_info&);
};
可以看到 type_info 類的構造函數/拷貝構造都為私有函數,也就是不允許使用者自己來建立type_info類對象。該類常用來判斷類型的方法就是 operator()== 和 operator()!= 。
下面測試代碼示範怎麼使用 typeid ,注意:必須借助 虛函數表 來實作類型推斷!
#include <iostream>
#include <functional>
using namespace std;
class Base{
public:
virtual
void test(){
cout << "Base::test" << endl;
}
};
class Derived : public Base{
public:
void test(){
cout << "Derived::test" << endl;
}
virtual
~Derived(){
cout << "Derived::~Derived" << endl;
}
};
int main()
{
Base base;
Derived derive;
Derived derive2;
Base* pBase = new Base();
Base* pBase2 = new Derived();
Derived* pDerive = new Derived();
//base和derive類型不同,傳回false
cout << "typeid(base) == typeid(derive) : " << (typeid(base) == typeid(derive)) << endl;
//derive和derive2類型相同,傳回true
cout << "typeid(derive2) == typeid(derive) : " << (typeid(derive2) == typeid(derive)) << endl;
//base和derive類型不同,傳回false
cout << "typeid(&base) == typeid(&derive) : " << (typeid(&base) == typeid(&derive)) << endl;
//derive和derive2類型相同,傳回true
cout << "typeid(&derive2) == typeid(&derive) : " << (typeid(&derive2) == typeid(&derive)) << endl;
//pBase和pDerive指向對象類型不同,傳回false
cout << "typeid(pBase) == typeid(pDerive) : " << (typeid(pBase) == typeid(pDerive)) << endl;
//pBase2和pDerive指向對象類型相同,傳回true
cout << "typeid(pBase2) == typeid(pDerive) : " << (typeid(*pBase2) == typeid(*pDerive)) << endl;
return 0;
}
大家可以測試一下,将 virtual 關鍵字注釋掉再試試,類型推斷結果會不一樣!以下為output資訊:
有虛函數的情況:
typeid(base) == typeid(derive) : 0
typeid(derive2) == typeid(derive) : 1
typeid(&base) == typeid(&derive) : 0
typeid(&derive2) == typeid(&derive) : 1
typeid(pBase) == typeid(pDerive) : 0
typeid(pBase2) == typeid(pDerive) : 1
沒有虛函數的情況:
typeid(base) == typeid(derive) : 0
typeid(derive2) == typeid(derive) : 1
typeid(&base) == typeid(&derive) : 0
typeid(&derive2) == typeid(&derive) : 1
typeid(pBase) == typeid(pDerive) : 0
typeid(pBase2) == typeid(pDerive) : 0
為什麼會這樣呢???
前面咱們是不是講到 virtual 是實作動态多态的手段,如果沒有虛函數表,那麼就可以在編譯時根據操作内容推導出類型資訊,以下靜态類型都能在編譯階段确定類型資訊:
- 類型名(例如:int、Base)
- 一個基本類型的變量(例如:2、3.0f)
- 一個具體的對象(例如:Base base;)
- 一個指向無虛函數表的類對象的指針
是以,當你對 typeid(pBase) 和 typeid(pBase2) 進行類型推導時,由于Base類中并不存在虛函數表,最終實際上和 typeid(Base*) 的效果一樣!也就是在編譯時就能夠确定操作數的類型!
那麼 typeid 是怎麼動态推導類型的呢?那肯定是和咱們的虛函數表有關系!前面咱們講過,typeid傳回的是type_info類對象的引用,也就是隻要是同一個類的執行個體,typeid 的傳回值應該都是相同的!
//typeid(pBase2) 和 typeid(pDerive) 傳回位址相同
cout << "typeid(pBase2) = " << &typeid(*pBase2) << " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
在main函數中添加以上測試代碼,(注意将 Base 類中的 virtual 關鍵字注釋取消,因為咱們要測試動态類型推導時的傳回位址),此時,測試代碼輸出的日志資訊為:
typeid(pBase2) = 0x564006a8ad20 typeid(pDerive) = 0x564006a8ad20
typeid 傳回的位址相同,是以你去比較這兩個對象的類型肯定也是相同的!
注意:此時你注釋掉virtual看看,是否還相同呢?想想為什麼?
typeid 的原理咱們先講到這,如果你是C++編譯器開發者,如果讓你去實作RTTI技術,你如何去确定一個對象的真實類型呢?
首先,每個類在編譯之後應該有一個 type_info 資料結構來辨別這個類的唯一性,這個 type_info 資料應該是确定的并在編譯時就建立出來了。
其次,typeid 的實作;顯示 typeid 的功能無非考慮兩種情況:
- 靜态:直接根據操作數(例如int、無虛函數表的類對象等)找到對應類型的 type_info 資料即可。
- 動态:此時想要傳回對象的真實類型隻能夠借助虛函數表了,那麼,虛函數表中應該能找到真實類型的 type_info 資料的入口(類似虛函數表存儲的是虛函數的位址,這裡應該也是存儲的是指向 type_info 資料的位址)。
ok,講到這裡,我們就需要深入挖掘虛函數表相關的知識,那麼你要先補充一下虛函數表相關知識。typeid 的動态類型推導與虛函數表的關系,咱們放到下一篇在來講!
Tips:可以先看看我的new[]/delete[],原理相通!
2、dynamic_cast
dynamic_cast運算符将一個指向基類的指針轉換成指向派生類的指針;如果失敗,傳回空指針。注意,主要用來進行向上轉換,也就是從父類指針轉換成子類指針(用來實作c++多态的),因為子類指針是不需要進行轉換就可以被指派給父類指針的!
好了嘛,既然是要實作多态,那麼這裡就必須借助虛函數表!也就是dynamic_cast在操作無虛函數表的對象時是不允許的!(會抛異常/編譯錯誤)
添加以下測試代碼:
//将父類指針轉為子類指針
auto p1 = dynamic_cast<Derived*>(pBase);
//将指向子類的父類指針轉為子類指針
auto p2 = dynamic_cast<Derived*>(pBase2);
cout << "p1 = " << p1 << " p2 = " << p2 << endl;
日志輸出:
p1 = 0 p2 = 0x556f565f4ed0
至此,咱們就講解完了C++中RTTI技術中兩個操作符的使用方式和原理!後面一篇咱們再對typeid和虛函數的關系做更進一步講解。
後一篇位址:typeid 和虛函數_master-計算機科學專欄-CSDN部落格
有問題請留言,歡迎轉發,請勿拷貝!