一、可变参数模板

1.1 基本语法及原理

C++11 支持可变数量参数的函数模板和类模板,我们统称为可变参数模板,可变数目的参数被称为参数包。根据模板类型,我们将参数包分为两种:模板参数包和函数参数包。其中模板参数包表示零或多个模板参数;函数参数包表示零或多个函数参数。其基本语法如下:

template <class ...Args> void Func(Args ... args) {}
template <class ...Args> void Func(Args& ... args) {}
template <class ...Args> void Func(Args&& ... args) {}

我们用省略号来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class … 或 typename … 指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟 … 指出接下来表示零个或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。
可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。我们通过一小段代码来简单理解一下,需要注意的是这里使用了sizeof… 运算符来计算参数包中参数的个数。

#include <iostream>
using namespace std;

template <class... Args>
void Print(Args&&... args)
{
	cout << sizeof...(args) <<endl;
}

int main()
{
	double x = 2.2;
	Print();  // 包中有0个参数
	Print(1);  // 包中有1个参数
	Print(1, string("xxxx"));  // 包中有2个参数
	Print(1.1, string("xxxxx"), x);  // 包中有3个参数
	 
	return 0;
}

4.2 包扩展

对于一个参数包,除了计算他的参数个数,更重要的是能够对其进行扩展。当扩展一个包时,我们需要提供用于每个扩展元素的模式,扩展一个包,就是将其分解为一个个构成它的元素,获得扩展后的列表。我们通过在模式的右边放一个省略号 (…) 来触发扩展操作。C++支持更加复杂的包扩展,直接将参数包依次展开作为实参给一个函数去处理。

4.3 emplace 系列接口

C++11 以后 STL 容器新增了 emplace 系列的接口,均为模板可变参数,功能上兼容 push 和 insert 系列,在此基础上支持新的功能。假设容器为container<T>,emplace 还支持直接插入构造 T 对象的参数,这样在某些场景中会更加高效一些,可以直接在容器空间上构造 T 对象。

template<class... Args> void emplace_back(Args&&... args);
template<class... Args> iterator emplace(const_iterator position, Args&&... args);

二、新的类功能

2.1 默认的移动构造和移动赋值

原来C++类中,有6个默认成员函数:构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载和 const 取地址重载,重要的是前面四个,后两个用处不大,默认成员函数我们不写编译器会生成一个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝复制重载中的任意一个,那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数、对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,反之则调用拷贝赋值。如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

2.2 成员变量声明时给缺省值

成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表时用缺省值初始化。

2.3 defult 和 delete

可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成。
如果想要限制某些默认函数的生成,在 C++98 中,是该函数设置成 private,并且只声明补丁而已,这样只要其他人想要调用就会报错。在C++11 中更简单,只需在该函数声明加上 =delete 即可,该语法指示编译器不生成对应函数的默认版本,称 =delete 修饰的函数为删除函数。

三、lambda 表达式

3.1 lambda 表达式语法

lambda 表达式本质上是一个匿名函数对象,与常规函数不同的是其可以定义在函数内部。由于lambda 表达式在语法使用上没有类型,我们一般是用 auto 或者模板参数定义的对象去接受 lambda 对象,其表达式如下:

[capture-list] (parameters)->reutrn type {function body}

每个部分的具体及功能如下

  • [capture-list]:捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据 [] 来判断接下来的代码是否胃 lambda 函数,捕捉列表能否为 lambda 函数使用,捕捉列表可以传值和传引用捕捉。捕捉列表为也不能省略。
  • (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要传递参数,则可以连同 () 一起省略。
  • -> return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可以省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {function body}:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用捕获到的变量,函数体为空也不能省略。

3.2 捕捉列表

lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。主要有三种捕捉方式。

更多推荐