static_assert 声明在编译时测试软件断言,这与在运行时进行测试的其他断言机制不同。 如果断言失败,则编译也将失败,且系统将发出指定的错误消息。
const int nValue = 3;
static_assert(nValue < 10, "Error");
这样编译时就会报出Error的错误提示信息。
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
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 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表达式具有如下形式:
[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
都能够返回它的实际类型.