c++11新特性
C++11特性划分为七大类,分别是:
1.保持语言的稳定性和兼容性
2.更倾向于通用的而不是特殊化的手段来实现特性
3.专家新手都支持
4.增强类型的安全性
5.增强性能和操作硬件的能力
6.开发能够改变人们思维方式的特性
7.融入编程现实
1、保持语言的稳定性和兼容性
(1)新增关键字
alignas
alignofdecltype
auto重新定义
static_assert
using重新定义
noexcept
export
nullptr
constexpr
thread_local
(2)返回函数名字
const char* hello(){return __func__;};
(3)#pragma once 等价于
#ifndef THIS_HEADER
#define THIS_HEADER
//...
#endif
(4)变长函数参数的宏定义,例子:
#define LOG(...) {\
fprintf(stderr, "%s: line %d: \t", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n") ; \
}
使用方式:
LOG("x=%d", x)
注意加上编译选项 g++ -std=c++11 xx.cpp
(5)__cplusplus 判断编译器类型, 其等于199711L(c++03) 201103L(c++11)
#if __cplusplus< 201103L
#error "should use c++11 implementation"
#endif
(6)运行时检查用assert, 编译时检测用static_assert(1==1, "information")
在c++11之前可以通过除0导致编译器出错的方式来模拟一个静态断言
#define assert_static(e) \
do{\
enum{assert_static__ == 1/(e)}; \
}while(0)
(7)noexcept
如果c++11中noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()来终止程序运行
c++11默认将delete函数设置成noexcept
类析构函数默认也是noexcept(false)的
这个用法可以推广到函数模板
template<class T>
void fun() noexcept(noexcept( T() ) ) ){}
这样可以根据条件决定“函数是否抛出异常”
(8)快速初始化成员变量
可以通过=、{}对非静态成员尽快就地初始化,以减少多个构造函数重复初始化变量的工作,注意初始化列表会覆盖就地初始化操作
class Group{
public:
Group(){}
Group(int a) : data(a) {}
Group(Mem m) : mem(m) {}
Group(int a, Mem m, string n) : data(a), mem(m), name(n) {}
private:
int data = 1;
Memmem{0};
string name("Group");
};
(9) sizeof可以对People::hand这样的类的非静态成员进行计算,而不用先新建一个对象People p了
(10)类模板可以声明友元了
template<typename T> class People[
friend T;
}
(11)通过final和override让编译器辅助做一些函数是否禁止重载或函数是否是重载函数的判断,
这样做的目的是避免——类继承者的编写者有时想重载某个基类的函数,结果由于参数写错或者函数名写错,导致意想不到的误会
(12)c++98中函数有默认参数,模板类有默认模板参数,但是函数模板没有,c++11增加了这一特性,但与类模板有区别
template<T1, T2=int> class xxx; //OK
template<T1 = int, T2> class xxx2; //error
template<T, int i=0> class xxx3; //OK
template<inti=0, T> class xxx4; //error
template<T1=int, T2> void fun(T1 a, T2 b); //OK
template<inti=0, T> void fun2(T a); //OK
(13)外部模板加快编译器编译速度,如下(当然不加也无妨)
extern template void fun<int>(int);
2、更倾向于通用的而不是特殊化的手段来实现特性
(1)继承构造函数
目的:由于类构造函数是不可继承的,为了避免派生类逐个实现基类的构造函数(透传)
例子:
struct A{
A(int i){}
A(double d, int i){}
A(float f, int i, const char* c){}
}
struct B:A{
using A::A;
}
等价于
struct B{
B(int i){}
B(double d, int i ){}
B(float f, int i, const char* c){}
}
(2)委派构造函数
目的:减少多构造函数的类编写时间,让复杂的构造函数调用简单的构造函数(通用函数),实现这个目的在以前通常采用一个通用的init的类成员函数来实现
例子:有点像变量初始化列表
class Info{
public:
Info() {InitRest(); }
Info(int i) : Info() {type = i;}
Info(char e) : Info() {name = e;}
private:
void InitRest() { //其他初始化操作}
int type{1};
char name{'a'};
};
需要注意的是, : Info()不能与初始化列表一起使用
如下面这段是错误的:
Info(int i) : Info(), type(i){}
(3)移动语义
目的:解决深拷贝问题,同时减少函数返回类对象时拷贝构造的次数,注意这里移动构造函数只对临时变量起作用,在之前的解决中都是通过手工定制拷贝构造函数来解决的
概念:
左值——可以取地址的、有名字的就是左值,如 a = b+c中的a
右值——不能取地址的、没有名字的就是右值,如a = b+c中的b+c
右值引用——就是对一个右值进行引用的类型,因为右值没有名字,下面代码是从右值表达式获取其引用:
T && a = ReturnRvalue();
可以理解左值引用是具有名字的变量名的别名,右值引用则是没有名字的变量名的别名,所以必须立即初始化(如上)
在c++98中,常量左值引用(const T&) 是一个“万能”的引用类型,它可以接受非常量左值(T a),常量左值(const T a),右值(T fun() )进行初始化
使用常量左值引用可以减少临时对象的开销,如下
struct Copyable
{
Copyable() {}
Copyable(const Copyable& o)
{
cout<< "Copied" <<endl;
}
};
Copyable ReturnRvalue() { return Copyable(); }
void AcceptVal(Copyable) {}
void AcceptRef(const Copyable& cp) {}//Copyable c = std::move(cp);}
void AcceptRRef(int&& i) {i+=3; cout<< (char)i<<endl;}
int main()
{
cout<< "Pass by value: " <<endl;
AcceptVal(ReturnRvalue()); // 临时值被拷贝传入
cout<< "Pass by reference: " <<endl;
AcceptRef(ReturnRvalue()); // 临时值被作为引用传递
AcceptRRef('c'); // 临时值被作为引用传递
}
例子:
①移动构造函数
#include <iostream>
using namespace std;
class HasPtrMem {
public:
HasPtrMem(): d(new int(3)) {
cout<< "Construct: " << ++n_cstr<<endl;
}
#if 0 //拷贝一份内存,并用h.d的值赋值
HasPtrMem(const HasPtrMem& h): d(new int(*h.d)) {
cout<< "Copy construct: " << ++n_cptr<<endl;
}
#endif
HasPtrMem(HasPtrMem&& h): d(h.d) { // 移动构造函数
h.d = nullptr; // 将临时值的指针成员置空
cout<< "Move construct: " << ++n_mvtr<<endl;
}
~HasPtrMem() {
delete d;
cout<< "Destruct: " << ++n_dstr<<endl;
}
int * d;
static int n_cstr;
static int n_dstr;
static int n_cptr;
static int n_mvtr;
};
int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;
int HasPtrMem::n_mvtr = 0;
const HasPtrMem GetTemp() {
const HasPtrMem h;
cout<< "Resource from " << __func__ << ": " << hex <<h.d<<endl;
return h;
}
int main() {
const HasPtrMem&& a = GetTemp(); //延长临临时变量的生命周期
cout<< "Resource from " << __func__ << ": " << hex <<a.d<<endl;
return 0;
}
②<utility>的函数std::move(T) 可以调用类T的移动构造函数,来完成对象的深度拷贝,在类没有定义移动构造函数的时候,会默认调用该类的拷贝构造函数
一个高性能的置换函数如下,调用时使用移动构造函数,移动赋值函数
class T
{
T(T&& t); //移动构造函数
T& operator=(T&& t); //移动赋值函数
T(const T& t); //拷贝构造函数
T& operator=(const T& t); //赋值函数
}
template<class T>
void swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
(4)完美转发
目的:完美转发的意思就是在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数,如:
template<typename T> void Iamforwording(T t){
IrunCodeActually(t);
}
为了使这个函数能够支持左值引用和右值引用, c++11里面定义了新的引用折叠规则(属于模板推导规则的一部分),详细的推导规则可以参见《深入理解c++11》一书P86的表3-2(属于模板的推导规则之一)
简单例子如下:
void IamForwording(X&&& t){
IrunCodeActually(static_cast<X&&&>(t));
}
应用引用折叠规则,则是
void IamForwording(X& t){
IrunCodeActually(static_cast<X&>(t));
}
例子:
void RunCode(int&& m) { cout<< "rvalue ref" <<endl; }
void RunCode(int& m) { cout<< "lvalue ref" <<endl; }
void RunCode(const int&& m) { cout<< "constrvalue ref" <<endl; }
void RunCode(const int& m) { cout<< "constlvalue ref" <<endl; }
template<typename T>
void PerfectForward(T&& t) { RunCode(forward<T>(t)); }
int main() {
int a;
int b;
const int c = 1;
const int d = 0;
PerfectForward(a); // lvalue ref
PerfectForward(move(b)); // rvalue ref
PerfectForward(c); // constlvalue ref
PerfectForward(move(d)); // constrvalue ref
}
(5)初始化列表
允许以下的初始化代码,而c++98只能通过第一行的编译
int a[] = {1,2,3};
int b[]{1,2,3};
vector<int> c{1,2,3};
map<int, float> d= {{1,1.0f}, {2, 2.0f}};
总结起来,以下初始化结果是等价的
int a= 3+4;
int a= {3+4};
int a(3+4);
int a{3+4};
int *pa = new int(3+4);
初始化列表也可以用于类的构造函数
enum Gender {boy, girl};
class People {
public:
People(initializer_list<pair<string, Gender>> l) // initializer_list的构造函数
{
autoi = l.begin();
for (;i != l.end(); ++i)
data.push_back(*i);
}
private:
vector<pair<string, Gender>> data;
};
People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};
(6)POD(plain old data)类型
POD通常是用户自定义的普通类型,体现了与C的兼容性,可以用最老的memcpy进行复制,可以用memset进行初始化
c++11将POD划分为两个基本概念的合集,即:平凡的(trivial) 和 标准布局的(standard layout)
平凡的类或结构体应符合以下定义:
①拥有平凡的默认构造函数和析构函数,一旦定义了构造函数,即使是一个无参数的空函数,该构造函数也不再是的平凡的。
②拥有平凡的拷贝构造函数和移动构造函数
③拥有平凡的拷贝赋值运算符和移动赋值运算符
④不能包含虚函数以及虚基类
可以通过
cout<<is_trivial<sturct XXX>::value <<endl
来检验,头文件<type_traits>
标准布局的类或结构体应符合以下定义:
①所有非静态成员有相同的访问权限
②在类或者结构体继承时,满足以下条件之一:
*派生类中有非静态成员,且只有一个仅包含静态成员的基类
*基类有非静态成员,而派生类没有非静态成员
③类中第一个非静态成员的类型与其基类不同
④没有虚函数和虚基类
⑤所有非静态数据成员均符合标准布局类型,其基类也符合标准布局
可以通过
cout<<is_standard<sturct XXX>::value<<endl
来检验,
头文件<type_traits>
(7)用户自定义常量
struct Watt{ unsigned int v; };
Watt operator "" _w(unsigned long long v) {
return {(unsigned int)v};
}
int main() {
Watt capacity = 1024_w;
}
(8)模板别名
typedef unsignd int UINT
等价于 using UINT = unsigned int
同样适用于模板
template<typename T> using MapString = std::map<T, char*> MapString<int> test;
3、专家新手都支持
(1)右尖括号改进, Y<X<1>>不再编译失败了。
(2)auto类型推导
重新定义了auto的语意,通过编译器实现对类型的自动推导,使c++有点类似python等动态类型语言
简单例子:
int main() {
double foo();
auto x = 1; // x的类型为int
auto y = foo(); // y的类型为double
struct m { int i; }str;
auto str1 = str; // str1的类型是struct m
auto z; // 无法推导,无法通过编译
z = x;
}
作用如下:
①简化代码
std::vector<std::string> t;
for(auto i=t.begin(); i<t.end(); i++){}
②免除类型声明麻烦
class PI {
public:
double operator* (float v) {
return (double)val * v; // 这里精度被扩展了
}
const float val = 3.1415927f;
};
int main() {
float radius = 1.7e10;
PI pi;
auto circumference = 2 * (pi * radius);
}
③跨平台
④模板推导,需要配合decltype
template<typename T1, typename T2>
auto Sum(T1& t1, T2& t2) ->decltype(t1+t2){
return t1+t2;
}
⑤用在宏定义上,避免多次变量展开
#define Max1(a, b) ((a) > (b)) ? (a) : (b)
#define Max2(a, b) ({ \
auto _a = (a); \
auto _b = (b); \
(_a > _b) ? _a: _b; })
int main() {
int m1 = Max1(1*2*3*4, 5+6+7+8);
int m2 = Max2(1*2*3*4, 5+6+7+8);
}
上述代码避免了,1*2*3*4被多次计算
(3)decltype,同样也是类型推导,区别在于
decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,其总是以一个普通表达式为参数,返回该表达式的类型。
作用如下:
①增加代码可读性
vector<int> vec;
typedef decltype(vec.begin()) vectype;
vectype i; // 这是auto无法做到的
for (i = vec.begin(); i<vec.end(); i++) {
}
for (decltype(vec)::iterator i = vec.begin(); i<vec.end(); i++) {
}
②恢复匿名结构体
sturct{
int d;
}ttt;
decltype(ttt) ok;
③模板利器
// s的类型被声明为decltype(t1 + t2)
template<typename T1, typename T2>
void Sum(T1 & t1, T2 & t2, decltype(t1 + t2) & s) {
s = t1 + t2;
}
④基于decltype的result_of
#include <type_traits>
using namespace std;
typedef double (*func)();
int main() {
result_of<func()>::type f; // 由func()推导其结果类型
}
⑤追踪函数的返回类型
下面两个代码是等价的,第二个代码可读性稍好
int (* (*pf()) () ) () { return 0;}
auto pf1() -> auto (*) () ->int (*) () { return 0; }
可用于函数转发:
#include <iostream>
using namespace std;
double foo(int a) {
return (double)a + 0.1;
}
int foo(double b) {
return (int)b;
}
template<class T>
auto Forward(T t) ->decltype(foo(t)){
return foo(t);
}
int main(){
cout<< Forward(2) <<endl; // 2.1
cout<< Forward(0.5) <<endl; // 0
}
推导法则:
首先定义标记符表达式:除去关键字、字面量等编译器需要使用的标记之外的程序员自定义的标记都可以是标记符。而耽搁标记符对应的表达式就是标记符表达式。
通过下面的例子和注释可以了解推导的四个法则。
int i = 4;
int arr[5] = {0};
int *ptr = arr;
struct S { double d; } s;
void Overloaded(int);
void Overloaded(char); // 重载的函数
int&& RvalRef();
const bool Func(int);
// 规则1: 单个标记符表达式以及访问类成员,推导为本类型
decltype(arr) var1; // int[5], 标记符表达式
decltype(ptr) var2; // int*, 标记符表达式
decltype(s.d) var4; // double, 成员访问表达式
decltype(Overloaded) var5; // 无法通过编译,是个重载的函数
// 规则2: 将亡值,推导为类型的右值引用
decltype(RvalRef()) var6 = 1; // int&&
// 规则3: 左值,推导为类型的引用
decltype(true ? i : i) var7 = i; // int&, 三元运算符,这里返回一个i的左值
decltype((i)) var8 = i; // int&, 带圆括号的左值
decltype(++i) var9 = i; // int&, ++i返回i的左值
decltype(arr[3]) var10 = i; // int& []操作返回左值
decltype(*ptr) var11 = i; // int& *操作返回左值
decltype("lval") var12 = "lval"; // const char(&)[9], 字符串字面常量为左值
// 规则4:以上都不是,推导为本类型
decltype(1) var13; // int, 除字符串外字面常量为右值
decltype(i++) var14; // int, i++返回右值
decltype((Func(1))) var15; // constbool, 圆括号可以忽略
(4)基于范围的for循环关键字改进
int arr[5] = {1,2,3,4,5};
for(auto i : arr)
cout<<i;
要求迭代的对象实现++和==等操作符,且范围是确定的。同时注意迭代器对象在for中是解引用的
vector<int> v = {1, 2, 3, 4, 5};
for (auto i = v.begin(); i != v.end(); ++i)
cout<< *i<<endl; // i是迭代器对象
for (auto e: v)
cout<< e <<endl; // e是解引用后的对象
4、增强类型的安全性
(1)强类型枚举
(2)堆内存管理
5、增强性能和操作硬件的能力
(1)常量表达式
(2)变长模板
(3)原子类型与原子操作 aotmic<float>af{1.2f}
(4)线程局部存储, 原来的__thread interrCode 可以写成intthread_localerrCode;
(5)快速退出:quick_exit与at_quick_exit 不执行析构函数而只是让程序终止,与exit同属于正常退出
用法类似下面:
#include <cstdlib>
#include <iostream>
using namespace std;
void openDevice() { cout<< "device is opened." <<endl; }
void resetDeviceStat() { cout<< "device stat is reset." <<endl; }
void closeDevice() { cout<< "device is closed." <<endl; }
int main() {
atexit(closeDevice);
atexit(resetDeviceStat);
openDevice();
exit(0);
}
输出:
device is opened
device stat is reset
device is closed
6、开发能够改变人们思维方式的特性
(1)nullptr
(2)=default =deleted
(3)lambda
lambda语法
[捕捉列表] (参数) mutable修饰符 -> 返回类型 {}
如果不需要参数,其括号可以省略
捕捉列表用于捕捉上下文的变量,形式如下:
[var]表示值传递方式捕捉变量var
[=] 表示值传递方式捕捉所有父作用域的变量(包括this)
[&var] 表示引用传递捕捉变量var
[&] 表示引用传递捕捉所有父作用域的变量(包括this)
[this] 表示值传递捕捉当前的this指针
①lambda 显然比仿函数(operator() (参数) )好用
class AirportPrice{
private:
float _dutyfreerate;
public:
AirportPrice(float rate): _dutyfreerate(rate){}
float operator()(float price) {
return price * (1 - _dutyfreerate/100);
}
};
int main(){
float tax_rate = 5.5f;
AirportPrice Changi(tax_rate);
auto Changi2 =
[tax_rate](float price)->float{ return price * (1 - tax_rate/100); };
float purchased = Changi(3699);
float purchased2 = Changi2(2899);
}
②代码可读性强
int Prioritize(int i);
int AllWorks(int times){
int i;
int x;
try {
for (i = 0; i < times; i++)
x += Prioritize(i);
}
catch(...) {
x = 0;
}
const int y = [=]{
int i, val;
try {
for (i = 0; i < times; i++)
val += Prioritize(i);
}
catch(...) {
val = 0;
}
return val;
}();
}
③捕捉列表不同会导致不同的结果
#include <iostream>
using namespace std;
int main() {
int j = 12;
auto by_val_lambda = [=] { return j ;};
auto by_ref_lambda = [&] { return j;};
cout<< "by_val_lambda: " <<by_val_lambda() <<endl; //12
cout<< "by_ref_lambda: " <<by_ref_lambda() <<endl; //12
j++;
cout<< "by_val_lambda: " <<by_val_lambda() <<endl; //12, 注意这里,j传入的时候是12
cout<< "by_ref_lambda: " <<by_ref_lambda() <<endl; //13
}
④mutable的作用
int main(){
int val;
// 编译失败, 在const的lambda中修改常量
auto const_val_lambda = [=]() { val = 3;};
// 非const的lambda,可以修改常量数据
auto mutable_val_lambda = [=]() mutable { val = 3;};
// 依然是const的lambda,对引用不影响
autoconst_ref_lambda = [&] { val = 3;};
// 依然是const的lambda,通过参数传递val
auto const_param_lambda = [&](int v) { v = 3;};
const_param_lambda(val);
return 0;
}
⑤与STL结合
#include <vector>
#include <algorithm>
using namespace std;
vector<int>nums;
vector<int>largeNums;
const int ubound = 10;
inline void LargeNumsFunc(inti){
if (i>ubound)
largeNums.push_back(i);
}
void Above() {
// 传统的for循环
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
if (*itr>= ubound)
largeNums.push_back(*itr);
}
// 使用函数指针
for_each(nums.begin(), nums.end(), LargeNumsFunc);
// 使用lambda函数和算法for_each
for_each(nums.begin(), nums.end(), [=](inti){
if (i>ubound)
largeNums.push_back(i);
});
}
7、融入编程现实
(1)algnof和alognas
(2)[[ attibute-list ]]
(3)Unicode的库支持
(4)原生字符串字面量