C++11中的技術剖析(萃取技術)


從C++98開始萃取在泛型編程中用的特別多,最經典的莫過於STL。STL中的拷貝首先通過萃取技術識別是否是已知並且支持memcpy類型,如果是則直接通過內存拷貝提高效率,否則就通過類的重載=運算符,相比之下就效率就低了一些。所以說有些做STL優化的程序員為了追求效率就直接改寫STL以便於支持可以通過memcpy的結構體,其根本就是利用了C++的萃取識別了自定義結構體。

C++11增加了移動拷貝,這使得很多時候程序執行效率大幅度提升,與之而來的左值右值總是讓初學者摸不清楚頭腦,如果遇到各種類型轉換只怕是惡心的只想放棄了。但是就我個人而言,因為之前學過蘋果的Object-C,曾經一度很羡慕OC中的各種炫酷的功能,但是后來看過C++11,感覺OC有些方面也不外如是。

閑話到此為止了,這里通過一個萬能引用的例子,講解一下C++11中一部分萃取技術。

對於函數:

template<typename T>
void logAndAdd(T &&t)
{
	if (std::is_same<T, int&>::value)
	{
		printf("左值引用類型\r\n");
	}
	else if (std::is_same<T, int>::value)
	{
		printf("右值引用類型\r\n");
	}
}

我們知道,t是一個萬能引用類型,因為這里涉及到類型推導,否則的話就是典型的右值引用。對於萬能引用,如果傳入的是右值,那么通過引用折疊,最終傳入的就是T&&類型,如果傳入的是左值,那么得到的就是T&類型。

如果按照以下方式調用上面函數,就會打出相應的結果,具體讀者可以自己調試:

int nA0 = 0;

int &nA1 = nA0;
logAndAdd(nA1);  // 傳入是左值,最終轉換成左值引用

logAndAdd(1);   // 傳入是右值,最終轉換成右值引用

is_same是個什么東西?其實這只是個很簡單很簡單的模板,實現如下:

template<class _Ty1,class _Ty2>
struct is_same : false_type
{
};

template<class _Ty1>
struct is_same<_Ty1, _Ty1> : true_type
{
};

template<class _Ty,_Ty _Val>
struct integral_constant
{
     static constexpr _Ty value = _Val;
     typedef _Ty value_type;
     typedef integral_constant<_Ty, _Val> type;

     constexpr operator value_type() const _NOEXCEPT
     {	
	return (value);
     }

     constexpr value_type operator()() const _NOEXCEPT
     {
	return (value);
     }
};

typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;

從中可以看出,_Ty1和_Ty2相等時構造的則是第二個結構體,反之則是第一個結構體。而所謂的返回值則是true_type或者false_type。當std::is_same<T, int&>其值為true_type時,其實就是構造了一個integral_constant<bool, true>臨時對象,而std::is_same<T, int&>::value的本質無非就是integral_constant<bool, true>構造的這個臨時對象中取出value這個值,而value在本例中的定義就是static constexpr _Ty value = _Val;其中_Ty為bool型。

也就是說std::is_same<T, int&>::value只是通過T, int&類型對比是否一致,然后根據結果構造了一個臨時對象,通過這個對象賦予初始類型和數值<bool, true>,從而返回了一個bool類型的值,再通過這個bool值的結果決定程序如何運行下去。

 

下面再看一個例子

template<typename T>
void logAndAddImp(T&& name, std::true_type)
{
	printf("logAndAddImp true_type\r\n");
}

template<typename T>
void logAndAddImp(T&& name, std::false_type)
{
	printf("logAndAddImp false_type\r\n");
}

template<typename T>
void logAndAdd(T &&t)
{
	if (std::is_same<std::remove_reference<T>::type, int>::value)
	{
		printf("T=int\r\n");
	}
	else if (std::is_same<std::remove_reference<T>::type, float>::value)
	{
		printf("T=float\r\n");
	}

	logAndAddImp(std::forward<T>(t), std::is_integral<typename std::remove_reference<T>::type>());
}

這里首先說一下std::is_integral,從字面意義上說,這里就是和之前判斷是否同一類型一樣。但是判斷首先會remove_reference移除原來類型上的引用屬性,const屬性和volatile屬性。也就是說,不管是int類型,int*,還是const int都會被判斷成int類型。源碼很簡單如下(因為篇幅,這里只復制一部分

template<class _Ty>
struct _Is_integral: false_type
{	
};

template<>
struct _Is_integral<char32_t>: true_type
{
};

template<>
struct _Is_integral<_LONGLONG>: true_type
{
};

template<>
struct _Is_integral<_ULONGLONG>: true_type
{	
};

true_type和false_type其實和之前一樣,而
std::is_integral<typename std::remove_reference<T>::type>()最終得到的結果,也和之前is_same一樣,是一個bool型的變量。但是從這里可以看到,只要是_Is_integral特化過的類型都會返回true,否則就為假。

這類萃取在實際代碼中非常之高效,以VS2015為例,編譯以下代碼:

template<typename T>
void logAndAdd(T &&t)
{
	if (std::is_same<std::remove_reference<T>::type, int>::value)
	{
		printf("T=int\r\n");
	}
	else if (std::is_same<std::remove_reference<T>::type, float>::value)
	{
		printf("T=float\r\n");
	}
}


int main()
{
	const int i = 0;
	
	int nA0 = 0;
	//logAndAdd(nA0);

	int &nA1 = nA0;
	logAndAdd(nA1);

	logAndAdd(1);

	const int &nA2 = 0;
	logAndAdd(nA2);

	volatile int nA3 = 0;
	logAndAdd(nA3);

	float t = 0.1f;
	logAndAdd(t);

	getchar();
	return 0;
}

 最終得到的release版本exe,反匯編如下所示:

.text:00401000 ; int __cdecl main()
.text:00401000 _main           proc near               ; CODE XREF: __scrt_common_main_seh+F4p
.text:00401000
.text:00401000 nA0             = dword ptr -0Ch
.text:00401000 nA3             = dword ptr -8
.text:00401000 var_4           = dword ptr -4
.text:00401000
.text:00401000                 push    ebp
.text:00401001                 mov     ebp, esp
.text:00401003                 sub     esp, 0Ch
.text:00401006                 mov     eax, ___security_cookie
.text:0040100B                 xor     eax, ebp
.text:0040100D                 mov     [ebp+var_4], eax
.text:00401010                 push    offset _Format  ; "T=int\r\n"
.text:00401015                 mov     [ebp+nA0], 0
.text:0040101C                 call    _printf
.text:00401021                 push    offset _Format  ; "T=int\r\n"
.text:00401026                 call    _printf
.text:0040102B                 mov     [ebp+nA3], 0
.text:00401032                 push    offset aTFloat  ; "T=float\r\n"
.text:00401037                 mov     [ebp+nA3], 0
.text:0040103E                 call    _printf
.text:00401043                 add     esp, 0Ch
.text:00401046                 call    ds:__imp__getchar
.text:0040104C                 mov     ecx, [ebp+var_4]
.text:0040104F                 xor     eax, eax
.text:00401051                 xor     ecx, ebp        ; cookie
.text:00401053                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00401058                 mov     esp, ebp
.text:0040105A                 pop     ebp
.text:0040105B                 retn
.text:0040105B _main           endp

沒有任何判斷邏輯,純粹是全部被優化,提取出來需要打印的地方直接printf了,這也是泛型編程一個特別讓人着迷的地方。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM