本内容参考至《深入理解C++11:C++11新特性解析与应用》,内容记作笔记,上传以供参考,转载请声明出处。
1.增加了与c99兼容的宏
__STDC_HOSTED__ 如果编译器的目标系统环境中包含完整的标准C库,宏定义为1否则为0
__STDC__
__STDC_VERSION__
__STDC_ISO_10646__
2.__func__预定义标识符
该标识符基本功能是返回所在函数的名字,在c++11中允许使用在类或者结构体中
3._Pragma操作符
_Pragma操作符与#pragma的功能相同,格式为:_Pragma(字符串字面量)
与#pragma不同的是,_Pragma可以用在宏定义中
4.变长参数的宏定义和__VA_ARGS__
变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS__则可以在宏定义的实现部分替换省略号所代表的字符串
5.断言和静态断言
断言就是将一个返回值总是需要为真的判别式放在语句中,用于排除在设计的逻辑上不应该产生的情况,以迫使在该参数发生异常的时候程序退出,从而避免程序陷入逻辑的混乱
头文件<cassert>或者<assert.h> 不在std命名空间中
可以通过定义NDEBUG来禁用断言 但是需要注意的是要将该定义放在头文件之前
静态断言是编译时期的断言,用法:static_assert(表达式,提示),需要注意的是在编译器不能确定的值是不能用静态断言的
6.noexcept
在c++11中,放在函数声明后的throw(int,double)用来声明指出函数可能抛出异常的类型该语法被弃用,同时放在函数声明后的throw()用来声明不会抛出异常的语法也被弃用反之用noexcept代替
noexcept的用法有两种分别为:
1:直接写在函数声明后,相当于直接禁用了该函数抛出异常,如 int func() noexcept;
2: 直接写在函数声明后,但是可以传入一个常量表达式,常量表达式的真伪可以用来判断该函数是否抛出异常,如int func() noexcept(fasle);
noexcept可以用在模板中
7.快速初始化成员变量
在c++98中可以使用"="在类中的就地初始化部分变量,该方法要求较高:非静态成员变量的初始化必须在构造函数中进行,而静态成员变量不满足常量性也不可就地声明,常量性的静态成员变量也只有整型或者枚举性才可以就地初始化
在c++11中增加了{}初始化的方法,类内类外都通用
花括号{}初始化方法,可以在类中声明时直接初始化非静态成员类变量,而对于静态成员变量,c++11和c++98一样,保持不变
即这种写法在c++中是合法的:
class A{
A(int v): val_(v){}
};
class B{
A value_a_{10};
};
7.非静态成员的sizeof
在c++11中对类的非静态成员也是可以直接使用sizeof运算符的,而这在c++98中是不支持的,如一下操作在c++11中得以支持
class A{
public:
int value_a_;
}:
int num = sizeof(A::value_a_);
8.拓展的friend语法
在c++11中对friend关键字进行了拓展,支持了如下的操作,而这种操作在c++98中是不合法的
有了以上的拓展之后,friend语法可以直接用在类模板中
class A{};
class B{
friend A;
};
9.final和override
final关键字主要是用来阻止虚函数继续重写
override主要是用来描述该函数是重写了基类的虚函数,因此基类中必须有与此函数同名的虚函数
10.函数模板的默认模板参数
在c++11中,支持了函数模板可以带默认模板参数,并且默认模板参数可以不遵照“从右往左”的规定进行指定(当然在类模板当中是不可行的),即以下的语法在c++11中是合法的
template <class T = int, class T2, class T3 = double>
void func();
11.外部模板
可以通过模板显式实例化加extern关键字,来声明一个外部模板函数,这样可以减少编译器的优化时间,提高编译效率,用法如下
假设有函数模板如: template <typename T> int func(T){}
在a.cpp中,显式实例化以下模板函数
template int func<int>(int){}
则在b.cpp中,可以做如下声明
extern template int func<int>(int){}
12.局部和匿名类型作模板实参
在c++11中支持了局部和匿名类型作为模板的实参
template<typename T> class X{};
template<class T> void func(T t){};
struct A{};
typedef struct {}B;
void FuncTest()
{
X<A> a;
X<B> b;
}
13.继承构造函数
在c++中,派生类如果要使用基类的成员函数,可以通过using声明来完成,而在c++11中也支持对构造函数进行using声明使用
14.委派构造函数
直白点的说则为,在重载的构造函数的初始化列表中调用基准版本的构造函数,基准版本的构造函数中实现所有构造函数的共同代码即可
目标构造函数中产生的异常,可以在委派构造函数中捕捉并处理。
15.左值,右值,右值引用
(1)关于左值和右值,在c++中,广泛的认为,左值是可以取地址的、有名字的,而右值是不能取地址、没有名字的,在c++11中,右值由两个概念构成一个是将亡值(xvalue,eXpiring Value),另一个则是纯右值(prvalue,Pure Rvalue)。纯右值是c++98的右值概念
在c++11程序中,所有值都必属于左值、右值、将亡值之一。
(2)在c++11中提出了右值引用的概念,即右值引用是对一个右值进行引用的类型,右值引用相当于延长了右值的声明周期,只要引用的变量还存在,那么被引用的右值就会一直存活。关于左值引用和右值引用,区别如下:
1) 关于左值引用:
非常量左值引用可以引用的值的类型只有非常量左值,常量左值引用非常量左值、常量左值及右值
2) 关于右值引用:
右值引用不支持引用左值;非常量右值引用可以引用的值的类型只有非常量右值,常量右值引用非常量右值、常量右值
16.std::move 强制转化为右值
在c++11中,标准库中加入了std::move函数,该函数会将一个左值转化成一个右值,继而可以用于移动语义中。值得注意的是,在移动构造函数中,如果移动构造函数的参数列表中的右值引用参数加了const关键字进行修饰,那么该右值就变成了一个常量右值,函数内部无法对其进行修改,因此在实现移动语义时需要根据情况选择是否需要使用const关键字
17.完美转发之std::forward
18.显式转换操作符
在c++中,可以进行显式类型转换和隐式类型转换,在一些允许隐式转换的过程中,容易出现bug,所以可以使用explicit关键字对目标进行修饰,避免其允许隐式类型转换
19.初始化列表
在c++11中,基本上全面支持了{}初始化变量,可以简单理解为()可以用{}替换,即为列表初始化
列表初始化也是唯一一种可以防止类型收窄的初始化方式
20.POD类型
POD在C++中是非常重要的一个概念,通常用于说明一个类型的属性,尤其是用户自定义类型的属性
c++11将POD划分为两个基本概念的合集,即:平凡的(trivial)和标准布局的(standard layout)。
(1)一个平凡的类或结构体应该符合以下定义:
1)拥有平凡的默认构造函数(trivial constructor)和析构函数(trivialdestructor)。
2)拥有平凡的拷贝构造函数(trivial copy constructor)和移动构造函数(trivialmove constructor)。
3)拥有平凡的拷贝赋值运算符(trivial assignment operator)和移动赋值运算符(trivial move operator)。
4)不能包含虚函数以及虚基类。
(2)一个标准布局的类应该符合以下定义:
1)所有非静态成员有相同的访问权限(public, private, protected)。
2)在类或者结构体继承时,满足以下两种情况之一:
※ 派生类中有非静态成员,且只有一个仅包含静态成员的基类。
※ 基类有非静态成员,而派生类没有非静态成员。
3)类中第一个非静态成员的类型与其基类不同。
4)没有虚函数和虚基类。
5)所有非静态数据成员均符合标准布局类型,其基类也符合标准布局。这是一个递归的定义,没有什么好特别解释的。
符合POD的类型,有以下好处:
1)字节赋值,代码中我们可以安全地使用memset和memcpy对POD类型进行初始化和拷贝等操作。
2)提供对C内存布局兼容。C++程序可以与C函数进行相互操作,因为POD类型的数据在C与C++间的操作总是安全的。
3)保证了静态初始化的安全有效。静态初始化在很多时候能够提高程序的性能,而POD类型的对象初始化往往更加简单(比如放入目标文件的.bss段,在初始化中直接被赋0)。
21.非受限联合体
在C++11标准中,取消了联合体对于数据成员类型的限制。标准规定,任何非引用类型都可以成为联合体的数据成员,这样的联合体即所谓的非受限联合体(Unrestricted Union)。同时联合体也可以拥有静态成员,但是如果联合体拥有静态成员变量,那么该联合体将共享一个值,而静态成员函数的作用大概是为了返回一个常数,这种写法看起来的更像是联合体是一个作用域。
22.用户自定义字面量
实际为重载字面量操作符""函数,利用该函数解析相关字符串,从而得到一个零时变量。
C++11中具体规则如下:
※ 如果字面量为整型数,那么字面量操作符函数只可接受unsigned longlong或者const char*为其参数。当unsigned long long无法容纳该字面量的时候,编译器会自动将该字面量转化为以\0为结束符的字符串,并调用以const char*为参数的版本进行处理。
※ 如果字面量为浮点型数,则字面量操作符函数只可接受long double或者const char*为参数。const char*版本的调用规则同整型的一样(过长则使用const char*版本)。
※ 如果字面量为字符串,则字面量操作符函数函数只可接受const char*,size_t为参数(已知长度的字符串)。
※ 如果字面量为字符,则字面量操作符函数只可接受一个char为参数。
总体上来说,用户自定义字面量为代码书写带来了极大的便利。此外,在使用这个特性的时候,应该注意以下几点:
※ 在字面量操作符函数的声明中,operator ""与用户自定义后缀之间必须有空格。
※ 后缀建议以下划线开始。不宜使用非下划线后缀的用户自定义字符串常量,否则会被编译器警告。当然,这也很好理解,因为如果重用形如201203L这样的字面量,后缀“L”无疑会引起一些混乱的状况。为了避免混乱,程序员最好只使用下划线开始的后缀名。
23.内联命名空间
在c++98中,不允许在不同的命名空间中对模板进行特化,因此在c++0x中,引入了内联命名空间,通过关键字"inline namespace"就可以声明一个内联命名空间。内联的命名空间允许程序员在父命名空间定义或者特化子命名空间的模板。
24.using定义类型别名
在c++11中,using也可以用来定义类型的别名,并且使用比typedef更加灵敏
25.右尖括号>的改进
在c++11中,两个连续的>>会根据语境智能判断该符号是否是右移符号,不再需要像c++98中那样在部分语境下将>>分开写。
26.auto关键字的改进
auto关键字在c++0x中赋予了新的意义,auto在c++0x中作为一个新的类型指示符(type-specifier,如int、float等都是类型指示符)来指示编译器,auto声明变量的类型必须由编译器在编译时期推导而得。
auto并非一种“类型”声明,而是一个类型声明时的“占位符”,编译器在编译时期会将auto替代为变量实际的类型。
27.decltype
decltype也可以进行类型推导,decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,decltype总是以一个普通的表达式为参数,返回该表达式的类型。而与auto相同的是,作为一个类型指示符,decltype可以将获得的类型来定义另外一个变量。与auto相同,decltype类型推导也是在编译时进行的。
当程序员用decltype(e)来获取类型时,编译器将依序判断以下四规则:
1)如果e是一个没有带括号的标记符表达式(id-expression)或者类成员访问表达式,那么decltype(e)就是e所命名的实体的类型。此外,如果e是一个被重载的函数,则会导致编译时错误。
2)否则,假设e的类型是T,如果e是一个将亡值(xvalue),那么decltype(e)为T&&。
3)否则,假设e的类型是T,如果e是一个左值,则decltype(e)为T&。
4)否则,假设e的类型是T,则decltype(e)为T。
28.后置返回类型
auto和decltype可以结合起来用在泛型编程中,方便类型的确定,例如可以这样
template<class A, class B>
auto func(const A& parm_a, const B& parm_b)->decltype(parm_a + parm_b)
{
return parm_a + parm_b;
}
29.强制枚举类型
非强类型作用域,允许隐式转换为整型,占用存储空间及符号性不确定,都是枚举类的缺点。针对这些缺点,新标准C++11引入了一种新的枚举类型,即“枚举类”,又称“强类型枚举” (strong-typed enum)。
声明强类型枚举非常简单,只需要在enum后加上关键字class。比如声明一个强类型的枚举Type:
enum class Tple {A, B, C, D}
强类型枚举具有以下几点优势:
※ 强作用域,强类型枚举成员的名称不会被输出到其父作用域空间。
※ 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换。
※ 可以指定底层类型。强类型枚举默认的底层类型为int,但也可以显式地指定底层类型,具体方法为在枚举名称后面加上“:type”,其中type可以是除 wchar_t以外的任何整型。比如指定Type是基于char类型的强类型枚举:
enum class Tple : char {A, B, C, D}
强类型枚举成员的名字不会输出到父作用域,枚举成员间仍然可以进行数值式的比较,但不能够隐式地转为int型,果要将强类型枚举转化为其他类型,必须进行显式转换。
另外在使用强制型枚举时,用class声明和struct声明两者没有任何区别。
c++11对原本的enum做了向前兼容,保留其枚举成员的名字会自动输出到父作用域,同时也做了向后兼容,可以为原本的enum显式的指定其底层类型。
30.智能指针
c++11中,废弃了c++98中的auto_ptr,新增了unique_ptr,shared_ptr,weak_ptr三种智能指针。
(1)unique_ptr独占指针
该指针独占式拥有,保证同一时间内只有一个智能指针可以指向该对象。
(2)shared_ptr共享指针
该指针实现共享式拥有,多个指针可以指向相同对象,该对象和其他相关资源会在最后一个引用被销毁时释放。
(3)weak_ptr弱指针
该指针主要是为了配合shared_ptr使用,防止两个shared_ptr相互引用而内存泄露。weak_ptr可以转成shared_ptr。
31.常量表达式
(1)常量表达式函数
用constexpr关键字可以在编译时期对表达式函数进行值计算。常量表达式函数的要求非常严格,总结起来,大概有以下几点:
※ 函数体只有单一的return返回语句。不会产生实际代码的语句在常量表达式函数中可以使用例如:static_assert,using,typedef
※ 函数必须返回值(不能是void函数)。
※ 在使用前必须已有定义。
※ return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式。
(2)常量表达式值
※ constexpr是编译期的值,在全局的命名空间中,如果代码没有显式的使用其地址,编译器可以选择不为其生成数据,而const一定会生成数据。
※ 编译时的浮点常量表达式值的精度要至少等于(或者高于)运行时的浮点数常量的精度。
对于自定义类型的数据,如果要使其能被常量表达式修饰,则需要定义自定义常量构造函数
struct MyType
{
constexpr MyType(int x): i(x){} int i;
};
constexpr MyType mt = {0};
自定义类型的常量表达式的构造函数有以下两点约束:
-
函数体必须为空
-
初始化列表只能由常量表达式来赋值
(3)常量表达式用于模板函数
C++11标准规定,当声明为常量表达式的模板函数后,而某个该模板函数的实例化结果不满足常量表达式的需求的话,constexpr会被自动忽略,该实例化后的函数将成为一个普通函数。
struct NotLiteral{ NotLiteral(){ i = 5; } int i; };
NotLiteral nl;
template <typename T>
constexpr T ConstExp(T t) { return t; }
void g()
{
NotLiteral nl;
NotLiteral nl1 = ConstExp(nl); // 该函数是一个普通函数,不再是一个常量表达式函数
constexpr NotLiteral nl2 = ConstExp(nl); // 无法通过编译,因为自定义类型未声明成constexpr构造函数
constexpr int a = ConstExp(1);
}
32.变长模板
(1)变长模板参数包和函数参数包
以tuple为例,可以通过以下声明tuple是一个变长类模板
template <typename... Elements> class tuple;
在标示符Elements之前的使用了省略号(三个“点”)来表示该参数是变长的。在C++11中,Elements被称作是一个“模板参数包”,这是一种新的模板参数类型。与普通的模板参数类似,模板参数包也可以是非类型的,例如
template<int... A> class NonTypeVariadicTemplate{};
NonTypeVariadicTemplate<1, 2, 3> nnn;
//实例出来的模板类是 NonTypeVariadicTemplate<int, int, int> nnn;
一个模板参数包在模板推导时会被认为是模板的单个参数(虽然实际上它将会打包任意数量的实参)。为了使用模板参数包,我们总是需要将其解包(unpack)。在C++11中,这通常是通过一个名为包扩展(pack expansion)的表达式来完成。比如:
template<typename... A>
class Template: private B<A...>
{
};
在C++11中,我们还可以声明变长模板的函数。对于变长模板函数而言,除了声明可以容纳变长个模板参数的模板参数包之外,相应地,变长的函数参数也可以声明成函数参数包(function parameter pack)。比如:
template<typename ... T>
void f(T... args);
值得注意的是,在C++11中,标准要求函数参数包必须唯一,且是函数的最后一个参数(模板参数包没有这样的要求)。
(2)变长模板进阶
标准定义了7种参数包可以展开的位置:
1.表达式 2.初始化列表 3.基类描述列表 4.类成员初始化列表 5.模板参数列表 6.通用属性列表 7.lambda函数的捕捉列表
在C++11中,标准还引入了新操作符“sizeof...”,其作用是计算参数包中的参数个数。
33.原子类型与原子操作
(1)C++11原子类型
原子类型和内置类型对应表
也可以使用atomic类模板定义原子类型,需要注意的是,atomic模板类的拷贝构造函数、移动构造函数、operator=等总是默认被删除的
std::atomic<T> t;
atomic类型的操作
(2)内存模型,顺序一致性与memory_order
默认情况下,在C++11中的原子类型的变量在线程中总是保持着顺序执行的特性(非原子类型则没有必要,因为不需要在线程间进行同步)。我们称这样的特性为“顺序一致”的,即代码在线程中运行的顺序与程序员看到的代码顺序一致。
大多数atomic原子操作都可以使用memory_order作为一个参数,在C++11中,标准一共定义了7种memory_order的枚举值
memory_order_seq_cst表示该原子操作必须是顺序一致的,这是C++11中所有atomic原子操作的默认值,不带memory_order参数的原子操作就是使用该值。
值得注意的是,并非每种memory_order都可以被atomic的成员使用。通常情况下,我们可以把atomic成员函数可使用的memory_order值分为以下3组:
❑ 原子存储操作(store)可以使用memorey_order_relaxed、memory_order_release、memory_order_seq_cst。
❑ 原子读取操作(load)可以使用memorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst。
❑ RMW操作(read-modify-write),即一些需要同时读写的操作,比如之前提过的atomic_flag类型的test_and_set()操作。又比如atomic类模板的atomic_compare_exchange()操作等都是需要同时读写的。RMW操作可以使用memorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cst。
(3)线程局部存储
线程局部存储(TLS, thread local storage)是一个已有的概念。简单地说,所谓线程局部存储变量,就是拥有线程生命期及线程可见性的变量。
C++11对TLS标准做出了一些统一的规定。与__thread修饰符类似,声明一个TLS变量的语法很简单,即通过thread_local修饰符声明变量即可。
int thread_local errCode;
一旦声明一个变量为thread_local,其值将在线程开始时被初始化,而在线程结束时,该值也将不再有效。对于thread_local变量地址取值(&),也只可以获得当前线程中的TLS变量的地址值。
34.快速退出quick_exit与at_quick_exit
在C++11中,标准引入了quick_exit函数,该函数并不执行析构函数而只是使程序终止。在C++11标准中,at_quick_exit和at_exit一样,标准要求编译器至少支持32个注册函数的调用。
35.nullptr和nullptr_t
C++11标准不仅定义了指针空值常量nullptr,也定义了其指针空值类型nullptr_t,也就表示了指针空值类型并非仅有nullptr一个实例。
36.默认函数的控制
C++11中在函数的定义或者声明加上“=delete”。“=delete”会指示编译器不生成函数的缺省版本。可以在默认函数定义或者声明时加上“=default”,从而显式地指示编译器生成该函数的默认版本。事实上,C++11标准称“= default”修饰的函数为显式缺省(explicit defaulted)函数,而称“= delete”修饰的函数为删除(deleted)函数。
37.lambda函数
lambda函数的语法定义如下:
[capture](parameters) mutable ->return-type{statement}
❑ [capture]:捕捉列表。捕捉列表总是出现在lambda函数的开始处。事实上,[]是lambda引出符。编译器根据该引出符判断接下来的代码是否是lambda函数。捕捉列表能够捕捉上下文中的变量以供lambda函数使用。
❑ (parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号()一起省略。
❑ mutable:mutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。
❑ ->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。出于方便,不需要返回值的时候也可以连同符号->一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。
❑ {statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。
在lambda函数的定义中,参数列表和返还类型都是可选的部分,而捕捉列表和函数体都可能为空。
语法上,捕捉列表由多个捕捉项组成,并以逗号分割。捕捉列表有如下几种形式:
❑ [var]表示值传递方式捕捉变量var。
❑ [=]表示值传递方式捕捉所有父作用域的变量(包括this)。
❑ [&var]表示引用传递捕捉变量var。
❑ [&]表示引用传递捕捉所有父作用域的变量(包括this)。
❑ [this]表示值传递方式捕捉当前的this指针。
在使用lambda函数的时候,如果需要捕捉的值成为lambda函数的常量,我们通常会使用按值传递的方式捕捉;反之,需要捕捉的值成为lambda函数运行时的变量(类似于参数的效果),则应该采用按引用方式进行捕捉。
38.数据对齐
C++11在新标准中为了支持对齐,主要引入两个关键字:操作符alignof、对齐描述符(alignment-specifier)alignas。
//alignof用法
#include <iostream>
using namespace std;
// 自定义的ColorVector,拥有32字节的数据
struct ColorVector
{
double r;
double g;
double b;
double a;
};
int main()
{
cout << "alignof(ColorVector): " << alignof(ColorVector) << endl; // alignof(ColorVector): 8
return 1;
}
//alignas用法
#include <iostream>
using namespace std;
// 自定义的ColorVector,对齐到32字节的边界
struct alignas(32) ColorVector
{
double r;
double g;
double b;
double a;
};
int main()
{
cout << "alignof(ColorVector): " << alignof(ColorVector) << endl;// alignof(ColorVector): 32
return 1;
}
在C++11标准中规定了一个“基本对齐值”(fundamental alignment)。一般情况下其值通常等于平台上支持的最大标量类型数据的对齐值(常常是long double)。我们可以通过alignof(std::max_align_t)来查询其值。而像我们在代码清单8-3中设定ColorVector对齐值到32字节(超过标准对齐)的做法称为扩展对齐(extended alignment)。不过即使使用了扩展对齐,也并非意味着程序员可以随心所欲。对于每个平台,系统能够支持的对齐值总是有限的,程序中如果声明了超过平台要求的对齐值,则按照C++标准该程序是不规范的(ill-formed),这可能会导致未知的编译时或者运行时错误。因此程序员应该定义合理的对齐值,否则可能会遇到一些麻烦。
对齐描述符可以作用于各种数据。具体来说,可以修饰变量、类的数据成员等,而位域(bit field)以及用register声明的变量则不可以。
39.通用属性
C++11语言中的通用属性使用了左右双中括号的形式: [[ attribute-list ]]
C++11预定义的通用属性包括[[ noreturn ]]和[[ carries_dependency ]]两种
[[noreturn]]主要用于标识那些不会将控制流返回给原调用函数的函数。
[[carries_dependency]]主要是为了解决弱内存模型平台上使用memory_order_consume内存顺序枚举问题。
40.原生字符串字面量
原生字符串使用户书写的字符串“所见即所得”,不再需要如'\t'、'\n'等控制字符来调整字符串中的格式
C++11中原生字符串的声明相当简单,程序员只需要在字符串前加入前缀,即字母R,并在引号中用使用括号左右标识,就可以声明该字符串字面量为原生字符串了。
#include <iostream>
using namespace std;
int main()
{
cout << R"(hello,\n
world)" << endl;
return 0;
} //编译选项: g++ 8-1-2.cpp -std=c++11
输出:
hello,\n
world