目录
一、新的默认成员函数
在C++11之前,默认成员函数只有六个,而C++11新增了两个默认成员函数,即移动构造函数和移动赋值运算符重载函数
在前面对右值引用的学习中我们已经见过了这两个函数,关于它们的一些特性还有需要注意的地方
- 如果用户没有显式实现一个移动构造函数,且没有显式实现析构函数、拷贝构造、赋值重载中的任意一个,那么编译器才会自动生成一个默认的移动构造函数
- 移动赋值重载函数与移动构造同理,只要四个函数都没有被实现,编译器才会生成默认的
- 移动构造函数会对内置类型成员进行逐字节的浅拷贝,对于自定义类型成员会调用该成员自己的移动构造,如果没有实现移动构造则调用其拷贝构造
- 移动赋值重载函数与移动构造同理,对内置类型成员进行浅拷贝,对于自定义类型成员会调用该成员自己的移动赋值,如果没有实现移动赋值则调用其拷贝赋值
- 如果用户已经实现了移动构造和移动赋值,编译器不会生成拷贝构造和拷贝赋值
关于右值引用,如果还有不了解的可以移步
二、新的关键字
2.1 default
前面提到,如果四个默认成员函数被实现了其中一个,编译器就不会生成默认的移动构造函数
但是假如我们就想让编译器给我们生成某个默认的成员函数呢?
在C++11中新增了default关键字,用来显式的指定让某个默认成员函数被生成
例如:
class A
{
public:
A(int x) //构造
:_a(x)
{}
A(const A& a) //拷贝构造
:_a(a._a)
{}
A(A&& a) = default; //强制生成默认移动构造
private:
int _a;
};
2.2 detele
与用于释放动态分配的内存空间的delete运算符进行区分,C++11新增的delete关键字与default的功能相反,其功能是可以禁止生成指定的函数。这个功能看似很鸡肋,其实大有妙用
如果我们想让一个类无法被实例化,该如何实现呢?以前我们需要将构造函数定义为私有成员,现在则只需要直接将这个类的构造函数用delete修饰即可,例如:
还有更巧妙的用法,我们知道如果一个函数的参数类型为int,我们还是可以把double类型的参数传入,因为会发生隐式类型转换
但是如果我们不想这样的事情发生,只希望传入的参数就是我们想要的类型,该如何实现呢?
我们只需要再声明一个完全一样的函数,把参数改为double,然后再用delete修饰即可,例如:
2.3 final和override
这两个关键字在我以前的文章中有提到,有兴趣的可以移步【C++】多态-CSDN博客https://blog.csdn.net/Eristic0618/article/details/137755151?spm=1001.2014.3001.5502
三、可变参数模板
可变参数列表,即长度不定的参数列表,在过去我们实际上已经接触过可变参数列表了,例如C语言的scanf和printf,其函数参数的数量是不定的
但是在过去,模板参数的数量一直是固定的,直到C++11出现了可变参数模板,模板才能支持可变参数列表。可变参数模板能够支持传入任意个数、任意类型的参数
3.1 定义
首先来看看如何声明一个支持可变参数模板的函数模板:
template <class... Args>
void Print(Args... a)
{}
在class或typename后加上省略号即可声明一个模板参数包。
在函数的参数列表中,通过在模板参数包后加上省略号即可声明一个函数形参参数包,其中包含任意数量的模板参数
但是我们无法通过类似下标等方式来获取其中的参数,只能通过特定方式将参数包展开来获取参数
3.2 递归展开参数包
我们知道,在调用一个函数的时候,我们传入的参数和函数的参数列表是要一一对应的。
利用这个性质,我们让函数的第一个参数是单独的参数,让第二个参数是一个参数包,这样,我们就可以每次将参数包内的第一个参数提取出来,剩余的参数进入第二个参数成为新的参数包
例如:
template<class T>
void Print(const T& val)
{
cout << val << endl;
}
template<class T, class... Args>
void Print(T val, Args... a)
{
cout << val << " ";
Print(a...);
}
int main()
{
Print(1, 2, 3, 4);
return 0;
}
像这样,当我们在主函数中传入4个参数,就会调用上面的第二个Print
数字1则进入函数的第一个参数,2、3、4进入第二个参数变为参数包
函数内部将1打印出来,剩余参数继续递归调用自己
等到最后只剩一个参数了,则会调用上面的只有一个参数的Print,结束递归
这种通过递归展开参数包的方式,就需要一个递归终止函数,也就是上面的第一个Print
除此之外,还有一种展开参数包的方式
3.3 逗号表达式展开参数包
这种方式则更为抽象一点,先来看看是如何用逗号表达式来展开一个参数包的
template<class T>
void Print(T& val)
{
cout << val << " ";
}
template<class... Args>
void Get(Args... a)
{
int arr[] = { (Print(a), 0)... };
cout << endl;
for (auto i : arr) //打印看看数组内的值
{
cout << i << " ";
}
}
int main()
{
Get(1, 2, 3, 4);
return 0;
}
首先,逗号表达式会从头到尾执行所有的表达式,其结果是最后一个表达式的值,所以 (Print(a), 0) 会执行一次Print函数并返回0。除此之外还用到了C++11的另一个特性即初始化列表,通过初始化列表来初始化一个变长数组,整个初始化列表{ (Print(a), 0)... }将会展开成sizeof(参数包)个逗号表达式,通过这种方式就可以在构造数组时展开参数包
如果你对上面的过程有疑惑,我们可以将逗号表达式中的Print函数和0换个位置,看看数组的内容会不会改变,前提是Print函数得返回一个值
可以看到,此时逗号表达式的结果就变为了Print函数的返回值,数组的内容也变为了1、2、3、4
3.4 emplace_back
C++11中一些容器多了名为emplace_back的接口,用于在尾部进行元素插入
有人会说,尾插直接用push_back不就好了?emplace_back的优势在哪呢?
首先我们可以看到emplace_back支持了可变参数模板和万能引用,而push_back的参数数量是固定的,这就导致如果容器元素的类型是pair的话,就必须提前用make_pair等构造好再传参
例如:
int main()
{
list<pair<int, int>> lt2;
lt2.push_back(make_pair(1, 1));
return 0;
}
如果换成emplace_back的话,我们就不需要提前构造,直接把参数传入即可,因为emplace_back使用了可变参数列表,不需像push_back一样一次只能传一个参数
完.