C++ 類型比較
有時候我們可能需要比較兩個變量的類型是否相等,在C++中有兩種辦法來檢測:
- 編譯器間檢測。
- 運行期間檢測。
1. 運行期間檢測
我們知道,在C++中,有一個運行時類型識別的技術,那就是針對需要的類型,編譯生成一個type_info
的類的全局對象來表示一個類型,當我們使用typeid
表達式的時候,編譯器就會返回指定的type_info
對象。例如如下:
int main(int args, char* argv[])
{
std::cout << typeid(NULL).name() << std::endl;
std::cout << typeid(nullptr).name() << std::endl;
if (typeid(NULL) == typeid(nullptr))
{
std::cout << "NULL == nullptr" << std::endl;
}
else
{
std::cout << "NULL != nullptr" << std::endl;
}
return 0;
}
輸出的結果爲:
int
std::nullptr_t
NULL != nullptr
我們可以看一下反彙編的代碼結果如下:
int main(int args, char* argv[])
{
00BF1C20 push ebp
00BF1C21 mov ebp,esp
00BF1C23 sub esp,40h
00BF1C26 push ebx
00BF1C27 push esi
00BF1C28 push edi
00BF1C29 mov ecx,offset _9AE661D0_cplusplus@cpp (0BFC008h)
00BF1C2E call @__CheckForDebuggerJustMyCode@4 (0BF1302h)
std::cout << typeid(NULL).name() << std::endl;
00BF1C33 push offset std::endl<char,std::char_traits<char> > (0BF1339h)
00BF1C38 mov ecx,offset int `RTTI Type Descriptor' (0BFA120h)
00BF1C3D call type_info::name (0BF14B5h)
00BF1C42 push eax
00BF1C43 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0BFB070h)]
00BF1C48 push eax
00BF1C49 call std::operator<<<std::char_traits<char> > (0BF1271h)
00BF1C4E add esp,8
00BF1C51 mov ecx,eax
00BF1C53 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0BFB06Ch)]
std::cout << typeid(nullptr).name() << std::endl;
00BF1C59 push offset std::endl<char,std::char_traits<char> > (0BF1339h)
std::cout << typeid(nullptr).name() << std::endl;
00BF1C5E mov ecx,offset std::nullptr_t `RTTI Type Descriptor' (0BFA12Ch)
00BF1C63 call type_info::name (0BF14B5h)
00BF1C68 push eax
00BF1C69 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0BFB070h)]
00BF1C6E push eax
00BF1C6F call std::operator<<<std::char_traits<char> > (0BF1271h)
00BF1C74 add esp,8
00BF1C77 mov ecx,eax
00BF1C79 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0BFB06Ch)]
if (typeid(NULL) == typeid(nullptr))
00BF1C7F push offset std::nullptr_t `RTTI Type Descriptor' (0BFA12Ch)
00BF1C84 mov ecx,offset int `RTTI Type Descriptor' (0BFA120h)
00BF1C89 call type_info::operator== (0BF14ABh)
00BF1C8E movzx eax,al
00BF1C91 test eax,eax
00BF1C93 je main+97h (0BF1CB7h)
{
std::cout << "NULL == nullptr" << std::endl;
00BF1C95 push offset std::endl<char,std::char_traits<char> > (0BF1339h)
00BF1C9A push offset string "NULL == nullptr" (0BF8B30h)
00BF1C9F mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0BFB070h)]
00BF1CA4 push eax
00BF1CA5 call std::operator<<<std::char_traits<char> > (0BF1271h)
00BF1CAA add esp,8
00BF1CAD mov ecx,eax
00BF1CAF call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0BFB06Ch)]
}
else
00BF1CB5 jmp main+0B7h (0BF1CD7h)
{
std::cout << "NULL != nullptr" << std::endl;
00BF1CB7 push offset std::endl<char,std::char_traits<char> > (0BF1339h)
00BF1CBC push offset string "NULL != nullptr" (0BF8B40h)
00BF1CC1 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0BFB070h)]
00BF1CC6 push eax
00BF1CC7 call std::operator<<<std::char_traits<char> > (0BF1271h)
{
std::cout << "NULL != nullptr" << std::endl;
00BF1CCC add esp,8
00BF1CCF mov ecx,eax
00BF1CD1 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0BFB06Ch)]
}
return 0;
00BF1CD7 xor eax,eax
}
00BF1CD9 pop edi
00BF1CDA pop esi
00BF1CDB pop ebx
00BF1CDC mov esp,ebp
00BF1CDE pop ebp
00BF1CDF ret
從call type_info::operator== (0BF14ABh)
我們知道,這個就是運行的時候判斷是否是相同的類型。
2. 編譯器識別
除了運行時類型識別,C++提供了編譯時的識別,對於編譯期的操作,在C++中最明顯的一個用途就是在模板中,同樣類型識別也是這樣,在c++中提供了std::is_same
來檢測是否是相同類型,例如:
int main(int args, char* argv[])
{
if (std::is_same<decltype(NULL), decltype(nullptr)>::value)
{
std::cout << "NULL == nullptr" << std::endl;
}
else
{
std::cout << "NULL != nullptr" << std::endl;
}
return 0;
}
返回結果如下:
NULL != nullptr
我們查看反彙編代碼:
int main(int args, char* argv[])
{
00812150 push ebp
00812151 mov ebp,esp
00812153 sub esp,40h
00812156 push ebx
00812157 push esi
00812158 push edi
00812159 mov ecx,offset _9AE661D0_cplusplus@cpp (081C008h)
0081215E call @__CheckForDebuggerJustMyCode@4 (0811302h)
if (std::is_same<decltype(NULL), decltype(nullptr)>::value)
00812163 xor eax,eax
00812165 je main+39h (0812189h)
{
std::cout << "NULL == nullptr" << std::endl;
00812167 push offset std::endl<char,std::char_traits<char> > (0811339h)
0081216C push offset string "NULL == nullptr" (0818B30h)
00812171 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (081B070h)]
00812176 push eax
00812177 call std::operator<<<std::char_traits<char> > (0811271h)
0081217C add esp,8
0081217F mov ecx,eax
00812181 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (081B06Ch)]
}
else
00812187 jmp main+59h (08121A9h)
{
std::cout << "NULL != nullptr" << std::endl;
00812189 push offset std::endl<char,std::char_traits<char> > (0811339h)
0081218E push offset string "NULL != nullptr" (0818B40h)
00812193 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (081B070h)]
00812198 push eax
00812199 call std::operator<<<std::char_traits<char> > (0811271h)
0081219E add esp,8
008121A1 mov ecx,eax
{
std::cout << "NULL != nullptr" << std::endl;
008121A3 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (081B06Ch)]
}
return 0;
008121A9 xor eax,eax
}
008121AB pop edi
008121AC pop esi
008121AD pop ebx
008121AE mov esp,ebp
008121B0 pop ebp
008121B1 ret
我們從兩個語句可以發現:
00812163 xor eax,eax
00812165 je main+39h (0812189h)
這個過程在編譯期間就完成了。
那麼編譯器識別是怎麼實現的呢?這裏其實就跟模板的偏特化有關:
template<class _Ty,
_Ty _Val>
struct integral_constant
{ // convenient template for integral constant types
static constexpr _Ty value = _Val;
using value_type = _Ty;
using type = integral_constant;
constexpr operator value_type() const noexcept
{ // return stored value
return (value);
}
_NODISCARD constexpr value_type operator()() const noexcept
{ // return stored value
return (value);
}
};
// ALIAS TEMPLATE bool_constant
template<bool _Val>
using bool_constant = integral_constant<bool, _Val>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
template<class _Ty1,
class _Ty2>
struct is_same
: false_type
{ // determine whether _Ty1 and _Ty2 are the same type
};
template<class _Ty1>
struct is_same<_Ty1, _Ty1>
: true_type
{ // determine whether _Ty1 and _Ty2 are the same type
};
其實偏特化的原理很簡單,就是使用template<class _Ty1> struct is_same<_Ty1, _Ty1>
代表同一個類型使用的類結構。