C++ 類型比較

C++ 類型比較

有時候我們可能需要比較兩個變量的類型是否相等,在C++中有兩種辦法來檢測:

  1. 編譯器間檢測。
  2. 運行期間檢測。

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  

我們從兩個語句可以發現:

  1. 00812163 xor eax,eax
  2. 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>代表同一個類型使用的類結構。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章