1. 类型推导
auto
- 原理:auto是一个占位符,在编译的时候由编译器推导得到具体的类型。
- 应用:变量的名字很长的时候,使用auot来代替。
- 优点:简化了开发量,开发时不需要写冗长的名字,使用auto代替,开发效率得到了提高。(在stl容器的迭代器上用的较多)
- 缺点:1. 新手如果使用auto,可能会搞不清楚底层发生了什么,当知道auto代表的什么,又不想写那么长的名字, 那就使用auto进行代替。2. 滥用auto可能会导致代码的可读性、可维护性降低。3. 需要定义变量才能得到数据类型。
decltype
- 原理:在编译器根据某个表达式、函数调用等,来推导出变量的类型。
- 应用:1. 在模板编程中,根据模板参数的成员变量来推到出类型。2. 函数的返回值推导。
- 优点:得到变量的类型是,不需要进行复制操作。可以保留CV限定符。
- 缺点:写起来较为麻烦,先进行类型推导,然后进行初始化。
auto和decltype实现的返回值类型推导
- 原理:auto + decltype,auto作为占位符,decltype推导出具体的返回值
- 应用:在模板函数编程中,返回值依赖于参数列表,如果把decltype放在前面,因为不能使用具体变量,并且参数可能没有无参构造函数,会比较难写,因此,增加了返回值类型后置。
- 优点:在返回值依赖于模板参数的情况下,写起来很方便
2. 模板改进
右括号优先被解析为模板结束符
- 好处:不需要在两个右括号中间加入不必要的空格,如果使用右移操作符号的话加括号
使用using,而不是typedef
- 好处:隔离开原名和新名,比typedef更加清晰,而且重定义一个模板的时候,不需要使用外敷类,很方便。
解除对函数模板参数设置默认值的限制
- 好处:使用默认模板的函数就像使用普通的函数一样
列表初始化的范围得到了增加
- 在C++11之前只能是对pod类型的数据进行初始化
- 在C++11之后,可以对任何类型的对象进行初始化,包括对象、堆上数组等。在对对象使用列表初始化的时候,会优先调用形参是initializer_list(不存储具体内容,仅仅持有数组的引用)的构造函数,这也是vector为什么可以使用初始化列表来初始化不定个数变量的原因。
- 好处:增加了初始化方式、可以防止类型收窄。
3. for范围循环语法糖
- 原理:基于for循环,对对象定义begin、end、!=三个方法就可以使用这这个语法糖,本质上是for循环。
- 应用:
for(auto i : v);
for(auto& i : v);
for(const auto& i : v);//注:v只被调用一次
- 好处:写起来方便、可维护性高。
4. std::function 和 std::bind
- std::function的好处:可以保存函数进行延迟执行,很适合作为回调函数。
- std::bind的好处:可以对函数的参数进行绑定,存储信息,并且可以组合多个函数的逻辑运算。比如可以绑定logic_add、less、greater函数来实现判断范围的函数。
5. lambda表达式
- 应用:定义匿名函数,可以按值、引用来捕获外部变量,默认是const的(因为是语法糖,相当于仿函数的成员函数operator小括号,捕获变量是仿函数的成员变量,opearator默认是const的,如果想要改变就添加mutable关键字)
- 好处:简洁,在需要的时间和地点定义函数,和上下文联系更加密切。不需要额外定义函数和函数对象,避免了代码膨胀,并且有好的可读性和可维护性。
6. 右值引用、std::move
右值引用
- 左值和右值的判断:左值是表达式结束后依旧存在的持久变量,右值是表达式结束后就不在存在的临时变量。判断左值和右值最直接的办法是看能不能对其取地址,如果能,就是左值,如果不能就是右值。
- 右值分为纯右值和将亡值。纯右值是非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、lambda表达式。将亡值是与右值引用相关的,如将要被移动的对象、T&& 函数返回值、std::move返回值。C++中的所有值都属于左值、纯右值、将亡值。三者之一。
- 右值引用的定义:对右值进行引用,右值因为没有名字,所以只能通过引用找到他。当引用完成之后,他本身就变成了一份左值。
- 右值引用的好处:提供一种构造方式,可以转移资源的所有权,而不是进行copy,减少了临时对象的创建、拷贝、销毁。因此对于动态申请和持有系统资源的类,要设计右值引用的拷贝构造函数和赋值函数,如果要在移动函数里面销毁资源,尽量不要这么做,而是交换资源,让临时变量去析构是销毁资源,这样在某些场景可以减少锁持有的时间。
std::move
- 原理:强制将左值转换成右值,也就是类型发生了变化。static_cast<T&&>(T)
- 目的:应用于移动函数,来转移资源。
- 使用场景:含有移动函数的类,对于没有移动函数的类或者基本数据类型,move无用处,仍然会发生内存的copy。
7. 完美转发
- 定义:对于模板函数,会按照参数原来的类型转发到另一个函数中。
- 原理:forward有两个函数,形参分别是左值和右值,返回值也分别是左值和右值。在应用的时候,会有两个模板参数,原模板参数的形参是T&&,也就是右值引用,如果是实参是左值,那么T就推断为T&,如果实参是右值,那么T被推断为T&&,在调用forward的时候,会调用特定的某个forward函数,也就会返回对应的值。另外两个forward函数的原理是static_cast,只是模板T不同,也就返回不同的T。这就是forward的原理。
8. emplace/emplace_back
- 好处:可以通过参数来构造对象,而不是拷贝,减少移动拷贝。也可以通过拷贝来构造对象。
- 何时使用emplace_back:构造参数,或者团队项目都是用emplace_back。
- 何时使用push_back:传递对象,并且团队都是用push_back,否则,我们应该引导大家去使用emplace_back。
- 何时不能用:没有对应的构造函数,就不能用参数版的emplace_back,但是仍然可以用emplace_back。
9. unordered_map
- 好处:在不需要排序的场景下,使用hash表,会很快,提高性能。
- 注意事项:对应自定义的key,需要定义hash函数或者比较函数。
10. 可变模板参数class/typename...
- 好处:模板参数的数量可变,扩展性更强
- 实现:对于可变模板函数,使用递归和逗号表达式来实现;对于可变模板参数类,使用递归和继承来实现。
可变参数模板和type_traits的其他应用:
- optonal:可以判断有没有值和取值
- 惰性求值lazy:真正用到的时候才去算或者申请资源
- dll帮助类:使用函数指针来泛化dll函数的调用
- lambda链式调用:嵌套
- any类:可以容纳任意类型的变量
- function_traits:萃取函数的信息
- variant:升级版的union,可以获取变量类型
- ScopeGuard:使用RAII机制,避免资源的泄露
- tuple_helper:帮助tuple来操作元素
使用智能指针防止内存泄露
有三种智能指针,分别是unique_ptr,shared_ptr,weak_ptr
-
unique_ptr:独占资源,可以初始化数组。
-
shared_ptr:共享资源,使用引用计数,引用计数为0的时候释放资源。需要注意一些问题:
- 管理数组对象时,要指定删除器,因为shared_ptr的默认删除器不支持数组。
- 不要用原始指针初始化shared_ptr,容易产生逻辑错误。
- 不要再函数实参中创建shared_ptr,因为函数参数计算顺序不固定,容易产生资源泄露。
- 如果要在对象内部返回this指针,使用shared_from_this。
- 避免循环引用,使用weak_ptr。
-
weak_ptr:构造函数不会增加引用计数,析构函数不会减少引用计数,作用是作为一个观察者来监视shared_ptr中管理的资源是否存在。使用expired()监视,使用weak.lock()来获取哦。
方便的多线程
- 使用thread传入函数和参数直接创建。要用join和detach操作对象。
- 使用mutex来线程同步。有互斥量、递归互斥量、超时互斥量、超时递归互斥量。但是尽量不要用递归互斥量,因为可以使用递归互斥量的一般都可以使用互斥量来优化。
- 条件变量condition_variable,使用wait等待,使用notify_one,notify_all通知。
- 原子变量,进行原子操作,不需要加锁。
- 异步操作类。future,get方法等待并获取结果,wait、wait_for方法阻塞等待。promise,将数据和promise绑定在一起。package_task,将函数和future绑定在一起。
- 异步操作函数。async直接创建或者延迟创建线程,并返回future,可以直接对future进行get、wait、wait_for操作,更简便。应该优先使用async取代线程的创建和异步操作。