首先為什么typeid()我們把它叫做操作符示括,而不是一個(gè)函數(shù)呢秘通,答案是因?yàn)樗筒皇且粋€(gè)函數(shù)悠就;熟悉C/C++的童鞋立馬就會(huì)猜,它應(yīng)該是一個(gè)宏充易,宏展開后可能是一段代碼梗脾,或者調(diào)用另一個(gè)函數(shù);我們翻遍C++的代碼庫(kù)也找不到typeid的定義盹靴,也就是說它既不是一個(gè)宏也不是一個(gè)函數(shù)炸茧。
它是C++內(nèi)部定義的一個(gè)運(yùn)算符號(hào),屬于C++語言本身的特性稿静,不是庫(kù)梭冠,所以我們叫它操作符號(hào);這和C/C++的另一個(gè)操作符sizeof()是一樣的行為改备。他們都是編譯器負(fù)責(zé)解釋翻譯控漠,運(yùn)行時(shí)刻看不到他們的影子,在編譯后的匯編代碼里就找不到他們的影子了悬钳。
以代碼為例:
#include <stdio.h>
#include <string.h>
#include <typeinfo>
void foo() {
long a = sizeof(int);
const std::type_info & b = typeid(int);
printf("size=%d, name=[%s]\n", a, b.name());
}
int main(int argc, char * argv[]) {
foo();
return 0;
}
運(yùn)行結(jié)果為:
size=4, name=[i]
在看聲稱的foo()匯編代碼:
_Z3foov:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq $4, -16(%rbp)
movq $_ZTIi, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZNKSt9type_info4nameEv
movq %rax, %rdx
movq -16(%rbp), %rax
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf
leave
ret
這是完整的foo()匯編代碼盐捷,我們只關(guān)注其中的兩條指令:
movq $4, -16(%rbp)
movq $_ZTIi, -8(%rbp)
這兩條指令對(duì)應(yīng)的源代碼分別是:
long a = sizeof(int);
const std::type_info & b = typeid(int);
很神奇吧,生成的匯編指令比原高級(jí)語言C++還要簡(jiǎn)單默勾,第一條指令把int的大小4直接算出來賦給變量a碉渡,第二條指令也是把一個(gè)常量直接賦給變量b。因?yàn)榫幾g器在翻譯過程中當(dāng)碰到操作符sizeof和typeid的時(shí)候母剥,它就直接完成了這部分的計(jì)算功能滞诺,把結(jié)果賦得輸出。
另外我們?cè)谏傻膮R編代碼文件找不到常量$_ZTIi的定義环疼,這個(gè)常量定義在C++運(yùn)行庫(kù)里面习霹,所以也就是C++知道有這些定義才能直接用。也就是說C++編譯器生成的目標(biāo)文件依賴于C++編譯器提供的部分功能函數(shù)炫隶。
說到這兒淋叶,有些童鞋估計(jì)可能會(huì)想起來以前做項(xiàng)目的時(shí)候遇到過的問題,當(dāng)一個(gè)大的工程由多團(tuán)隊(duì)多人開發(fā)時(shí)等限,各自模塊驗(yàn)證都沒有問題爸吮,集成的時(shí)候總是莫名其妙的錯(cuò)誤芬膝,crash等等,原因當(dāng)然很多了形娇,而如果不同的團(tuán)隊(duì)使用不同的編譯器生成目標(biāo)代碼锰霜,運(yùn)行庫(kù)時(shí),當(dāng)這些模塊集成到一起的時(shí)候可能就會(huì)由于不同的編譯器對(duì)各自運(yùn)行庫(kù)的需求不一致桐早,導(dǎo)致莫名錯(cuò)誤癣缅。
接著討論typeid()如何工作的?typeid的功能是用來得到一個(gè)對(duì)象的類型定義哄酝,據(jù)此判斷兩個(gè)類型是否一致友存。
std::type_info 是在/usr/include/c++/4.4.4/typeinfo里面定義的一個(gè)類,這個(gè)類只有一個(gè)成員const char *__name陶衅,加上一個(gè)虛函數(shù)表指針std::type_info的實(shí)際內(nèi)存大小為16屡立。
看其中定義的兩個(gè)函數(shù):
const char* name() const;
返回類型的名字,即成員變量__name的值搀军。
bool operator==(const type_info& __arg) const;
比較兩個(gè)類型是否相同膨俐,它不是比較字符串__name的內(nèi)容值,而是比較兩個(gè)類型的__name是否指向同一個(gè)內(nèi)存罩句,當(dāng)然指向同一塊內(nèi)存必然是內(nèi)存值相同的焚刺。
if (typeid(TYPE1) == typeid(TYPE2)) {
...
}
else {
...
}
注意既然typeid是在編譯時(shí)刻確定的類型,那么在多態(tài)環(huán)境下门烂,并不能根據(jù)變量指針的實(shí)際類型返回乳愉,而是返回變量的申明類型:
include <stdio.h>
#include <typeinfo>
class A1 {};
class A2 : public A1 {};
void foo(A1 * a1) {
printf("a1=%s\n", typeid(a1).name());
}
int main(int argc, char * argv[])
{
A1 * a1 = new A1();
A2 * a2 = new A2();
foo(a1);
foo(a2);
return 0;
}
運(yùn)行結(jié)果為
a1=P2A1
a1=P2A1
可見foo()打印出來的結(jié)果都是A1而不管實(shí)際參數(shù)a1是一個(gè)A1類型對(duì)象,還是一個(gè)A2類型對(duì)象屯远。
最后我們看一個(gè)class A1和A2的type_info是怎么定義的蔓姚。
函數(shù)foo()如下:
_Z3fooP2A1:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
movl $_ZTIP2A1, %eax
movq %rax, %rdi
call _ZNKSt9type_info4nameEv
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf
leave
ret
其中$_ZTIP2A1就是類A1的type_info定義信息。
.weak _ZTSP2A1
_ZTSP2A1:
.string "P2A1"
.weak _ZTIP2A1
_ZTIP2A1:
.quad _ZTVN10__cxxabiv119__pointer_type_infoE+16
.quad _ZTSP2A1
_ZTIP2A1的內(nèi)容有兩個(gè)字段氓润,前面我們說過type_info有兩個(gè)成員赂乐,第一個(gè)是指向類type_info虛函數(shù)表的指針薯鳍,第二個(gè)是指向類型字符串名字的指針咖气。
前面我們提到類型int的type_info定義在生成的目標(biāo)文件里找不到,因?yàn)閕nt是系統(tǒng)類型挖滤,C++會(huì)把所有的內(nèi)置類型的type_info組織好定義在庫(kù)里面崩溪,而對(duì)于用戶自定義的類型的type_info,則在定義數(shù)據(jù)類型的時(shí)候生成斩松。
_ZNKSt9type_info4nameEv是type_info::name()函數(shù)的代碼伶唯,因?yàn)楹瘮?shù)name()聲明成為inline類型,所以匯編代碼在此處也可見惧盹。
結(jié)尾
其實(shí)typeid()在做項(xiàng)目過程中很少被直接使用到乳幸,我猜更多的應(yīng)用場(chǎng)景是C++內(nèi)部使用瞪讼,比如dynamic_cast函數(shù)。