【C++】C++11之新的类功能与可变参数模板

目录

一、新的默认成员函数

二、新的关键字

2.1 default

2.2 detele

2.3 final和override

三、可变参数模板

3.1 定义

3.2 递归展开参数包

3.3 逗号表达式展开参数包

3.4 emplace_back

一、新的默认成员函数

在C++11之前,默认成员函数只有六个,而C++11新增了两个默认成员函数,即移动构造函数和移动赋值运算符重载函数

在前面对右值引用的学习中我们已经见过了这两个函数,关于它们的一些特性还有需要注意的地方

  • 如果用户没有显式实现一个移动构造函数,且没有显式实现析构函数、拷贝构造、赋值重载中的任意一个,那么编译器才会自动生成一个默认的移动构造函数
  • 移动赋值重载函数与移动构造同理,只要四个函数都没有被实现,编译器才会生成默认的
  • 移动构造函数会对内置类型成员进行逐字节的浅拷贝,对于自定义类型成员会调用该成员自己的移动构造,如果没有实现移动构造则调用其拷贝构造
  • 移动赋值重载函数与移动构造同理,对内置类型成员进行浅拷贝,对于自定义类型成员会调用该成员自己的移动赋值,如果没有实现移动赋值则调用其拷贝赋值
  • 如果用户已经实现了移动构造和移动赋值,编译器不会生成拷贝构造拷贝赋值

关于右值引用,如果还有不了解的可以移步

【C++】C++11之右值引用-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Eristic0618/article/details/140826229?spm=1001.2014.3001.5502


二、新的关键字

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博客icon-default.png?t=N7T8https://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一样一次只能传一个参数

完. 

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值