static_assert 聲明
static_assert聲明在編譯時測試軟件斷言,這與在運行時進行測試的其他斷言機制不同。如果斷言失敗,則編譯也將失敗,且系統將發出指定的錯誤消息。
const int nValue = 3;
static_assert(nValue < 10, "Error");
這樣編譯時就會報出Error的錯誤提示信息。
decltype作為操作符
用於返回表達式的數據類型。
int Add(int a, int b) { return a+b; } double dVal = 0.1; const double dVal0 = 0.2; const double& dVal1 = &dVal; decltype(dVal) dVal2 = 0.3; // dVal2是double類型 decltype(0.2) dVal3 = 0.2; // dVal3是double類型 decltype(dVal1) dVal4 = &dVal0; //dVal4是const double&類型 decltype(Add(3, 4)) var = 4; // var是函數返回的類型即int類型 decltype((dVal)) dVal5 = &dVal; //decltype((variable))多括號結果永遠是引用 int odd[] = {1, 3, 5, 7, 9}; int even[] = {0, 2, 4, 6, 8}; typedef int ArrType[5]; ArrType *Fun1(int i) { return (i %2) ? &odd : &even; } decltype(odd) *Fun2(int i) { return (i %2) ? &odd : &even; } ArrType *arr1 =Fun1(1); ArrType *arr2 =Fun2(2);
Fun2使用decltype表示它的返回類型是個指針,並且該指針類型與odd類型一致。因為odd是數組,所以Fun2返回一個指定5個整數的數組的指針。因為decltype(odd)類型的結果是數組,所以如果想Fun2返回指針則必須在函數聲明時加上*符號。
使用尾置返回類型
尾置返回類型(trailing return type)是在形參列表後面以->符號開始標明函數的返回類型,並在函數返回類型處用auto代替。尾置返回類型即可以直接指明類型,也可以用decltype推出出類型。形式:
auto Function(int i)->int
auto Fun3(int i)->int(*)[5] // 返回指定數組的指針
int n = 10;
auto Function(int i)->decltype(n)
template
auto Function(T t, W w)->decltype(t+w)
{
return t +w;
}
// 如果是自定義類型,則應該重載+實現t+w
注:C++14中,已經將尾置返回類型去掉了,可以直接用auto推導出類型。
參考:msdn.microsoft.com/en-us/library/dd537655(v=vs.100).aspx
auto關鍵字
c++11修改了auto關鍵字的作用,auto類型說明符能讓編譯器替我們去分析表達式的初始化值來推導出變量的類型。
類型推導
int j = 0; auto n = 0; // 0默認是int類型 map>::iteratori = m.begin(); const int* const pInt =new int(2); auto *pAuto =pInt; // pAuto為int* cosnt類型,忽略頂層const保留底層const const auto& ref = 42;// 0K int Add(int a, intb){return a+b;} auto ret = Add(3,4); // ret是int auto m = j; // m是int類型 auto i = m.begin(); *pAuto = 4; // Error pAuto = new int(5); // OK auto和動態分配 auto pInt1 = new int(4); int nValue = 4; auto pInt2 = newauto(nValue); //以nValue類型動態申請內存並以nValue賦值 delete pInt2; // 以下是申請指向&nValue的指針 auto** ppInt = newauto(&nValue); int** ppInt = new int*; *ppInt = &nValue; delete ppInt;
注:VS2010中對auto的動態分配支持有Bug,delete時會報錯,所以VS2010中不允許使用此功能。
參考:https://msdn.microsoft.com/en-us/library/dd293667(v=vs.100).aspx
Lambda表達式
Lambda表達式就是匿名函數。Lambda表達式表示一個可調用的代碼單元。與其他函數一樣,Lambda具有一個返回類型、一個參數列表和一個參數體。一個Lambda表達式具有如下形式:
[capture list](parameterlist))->return type { function body}
Capture list(捕獲列表)Lambda函數中定義的局部變量列表,可以為空。
Parameter list、function body和普通函數一樣。
return type,尾置類型指明,一般情況可以沒有,編譯器會自動推導出返回類型。當函數體有多個返回值時,編譯會產生錯誤,需要指明返回類型。
string strRet = [](const string&str) { return "Hello from " + str; }("Lambda");
auto fun = [](const string&str)->string { return "Hello from " + str; };
strRet =fun("Lambda");
Lambda表達式可以作為函數參數,例如在算法函數中調用時:
int arr[10] = {0};
generate(arr,arr+10, []()->int { return rand() % 100; });
捕獲列表的使用,指出數組第1個大於10的元素.
int nFlag = 10;
int nArr[] = {5,3, 2, 11, 4, 22};
auto first =find_if(nArr, nArr+6, [nFlag](int nValue){return nValue > nFlag;});
右值引用
為了支持移動語義,C++11引入了新的引用類型——左值引用(RValue Reference)。右值引用,即綁定到右值的引用,通過&&來獲取右值的引用。
左值:有具體的名字,作用域不止當前語句。
右值:匿名、作用域僅在當前語句。
C++11裡面對此作出的定義是:Things that aredeclared as rvalue reference can be lvalues or rvalues. The distinguishingcriterion is: if it has a name, then it is an lvalue. Otherwise, it is anrvalue.
普通類型的常量都是左值,但是字符串常量因為生存周期是全局的,所以字符串常量是左值。
int&& nRRef = 1;
const string& strLRef = “LValue Reference”;
// nRRef雖然是左值引用,但它是具名的,所以nRRef是左值
右值引用:右值一旦離開當前語句,其生存期就會被銷毀。而右值引用則將右值的有效性移動到右值引用這個左值上。
通過右值引用的定義可以看出它的主要作用是將臨時變量的生存周期給轉移了,這樣就減少創建變量銷毀對象的損耗。
構造函數和賦值函數是創建對象最常用函數,也是右值引用發揮作用的地方。
移動構造函數和移動賦值函數
class CMyString { public: CMyString() { m_data =NULL; m_len =0; } CMyString(const char* p) { m_len =strlen (p); Init(p); } CMyString(const CMyString&& str) { m_len =str.m_len; m_data =str.m_data; str.m_data =NULL; std::cout<< "Copy Constructor is called! source: " << m_data<< std::endl; } CMyString& operator=(const CMyString&& str) { if (this!= &str) { m_len =str.m_len; m_data =str.m_data; str.m_data= NULL; } std::cout<< "Copy Assignment is called! source: " << m_data<< std::endl; return*this; } virtual~CMyString() { if(m_data) delete[] m_data; } private: voidInit(const char *s) { m_data =new char[m_len+1]; memcpy(m_data, s, m_len); m_data[m_len] = '\0'; } private: char* m_data; size_t m_len; }; CMyString GetMyString() { CMyString str= "abc"; returnstr; // A } int _tmain(int argc, _TCHAR* argv[]) { CMyStringmyStr; myStr = GetMyString(); // B:1個右值賦給1個左值 return 0; }
代碼A返回1個無名的臨時對象,其實就是返回1個右值,這裡就會調用右值構造函數.代碼B將返回的右值賦給左值,同樣調用右值賦值函數即移動賦值函數。
注:一旦資源完成移動(賦值)之後,源對象(即右值引用對象)不能再指向被移動的資源。正如上面的移動構造函數及移動賦值函數在完成指針轉移時,右值引用的指針必須指向NULL。
標准庫函數 std::move
移動構造函數和移動賦值函數只接受右值作為參數,而所有的具名對象都是左值。那麼能不能將左值當作右值來使用呢?答案當然是可以的。標准庫提供了std::move這個函數,它完成的作用只是一個類型轉換,即將左值類型轉換成右值引用類型。move函數是通過模板實現的,VS2010代碼如下:
// TEMPLATE _Remove_reference template struct _Remove_reference { // removereference typedef _Ty _Type; }; template struct _Remove_reference<_Ty&> { // removereference typedef _Ty _Type; }; template struct _Remove_reference<_Ty&&> { // removervalue reference typedef _Ty _Type; }; // TEMPLATE FUNCTION move template inline typenametr1::_Remove_reference<_Ty>::_Type&& move(_Ty&& _Arg) { // forward_Arg as movable return ((typenametr1::_Remove_reference<_Ty>::_Type&&)_Arg); }
上面這段代碼很簡單,卻有點難懂。其實這裡利用的是技術,也即利用了模板特化和偏特化。我們可以看一段簡單的示例來理解這段代碼的功能(詳細見我之前文章)
template< typename T > struct STRUCT_TYPE { typedef intMY_TYPE; typedef__int64 POWER_TYPE; }; template<> struct STRUCT_TYPE { typedef floatMY_TYPE; typedefdouble POWER_TYPE; }; template< typename T > struct STRUCT_ALGO { // 下面的Typename是指示T::MY_TYPE是一個類型而不是成員變量 typedeftypename STRUCT_TYPE::MY_TYPE myType; typedeftypename STRUCT_TYPE::POWER_TYPE powType; powTypeGetPow(const myType& value) { returnvalue*value; } }; int _tmain(int argc, _TCHAR* argv[]) { __int64nPow = STRUCT_ALGO::GetPow(4); double dPow =STRUCT_ALGO::GetPow(5.0); return 0; }
通過上面的代碼可以看出,通過不同的模板形參,調用不同模板裡面的typedef類型,這樣可以達到不同類型模板形參數使用相同typedef名,共用代碼。
我們再來看move函數的實現,其實也是一樣的。
一開始創建通脹模板_Remove_reference,然後利用模板偏特化,這裡只部分特化左值引用和右值引用特性。所以模板形參不能為空,如果是全特化的參數則省略。
首先了解下引用疊加的規則:
X& &、X&& &、X& &&均疊加成X&
X&& &&則疊加成X&&
string str1 = “abc”;
string&& str2 = std::move(string(”abc”));
string&& str3 = std::move(str1);
string&& str4 = std::move(str3);
string(“abc”)實參明顯是一個右值,則調用通用模板。
str1是1個左值,move(&&)函數則會將類型推導為左值引用。
str3則是1個右值引用,則move函數將會將其類型推導為右值引用。
_Remove_reference就達到去除引用的作用。
簡單理解就是move(&&)可以接受所有類型的參數,無論是左值、右值、左值引用、右值引用均可。
(typenametr1::_Remove_reference<_Ty>::_Type&&)則是去掉引用,然後強制類型轉換成右值引用。所有類型都可以強制轉換成右值引用。
5.標准庫函數 std::forward
有些函數需要將其實參連同類型不變地轉發給其他函數,包括實參的是否cosnt、
左值還是右值。函數std::forward就是為了完成這個功能而創建的。
forward在VS2010中的實現如下:
//TEMPLATE CLASS identity template structidentity { // map _Ty to type unchanged typedef_Ty type; const_Ty& operator()(const _Ty& _Left) const { // apply identity operator to operand return(_Left); } }; //TEMPLATE FUNCTION forward template inline _Ty&&forward(typename identity<_Ty>::type& _Arg) { // forward _Arg, given explicitly specifiedtype parameter return((_Ty&&)_Arg); }
如果一個函數參數是指向模板類型參數的右值引用(如T&&),它對應的const屬性、左值、右值屬於將得到保持。
void Add(const int& i) { cout << "inner(const X&)" << endl; } void Add(int& i) { i++; cout << "inner(const X&)" << endl; } void Add(int&& i) { cout << "inner(X&&)" << endl; } template void AllAdd(T&& t) { Add(t); // 調用1 Add(forward(t)); // 調用2 } int_tmain(int argc, _TCHAR* argv[]) { int nTemp = 3; const int nValue = nTemp; AddAll(nValue); // 1, 2調用是相同的,均調用void Add(const int& i) AddAll(nTemp); // 1, 2調用是相同的,均調用void Add(int& i) AddAll(int(1)); // 1調用的是void Add(int& i),因為形參t有名字,是左值,故優先調用相應引用 // 2調用void Add(int&& i),因為forward能夠推導出T是右值引用 return 0; }
通過上面的示例就能夠很好的理解std::forward所謂的完美轉發了,因為只要是通過T&&的形參,forward都能夠返回它的實際類型.