C++11 -- 可变参数模板

可变参数模板的概念

( 1 ): C++98/01中,类模板和函数模板只能包含固定数量的模板参数.

( 2 ): 相比于C++98/01,C++11的新特性可变模板参数模板能够让我们创建可以接受可变参数的函数模板和类模板.

可变模板参数的定义

以下就是一个基本可变参数的函数模板.

//Args是一个模板参数包,args是一个函数形参参数包.
//声明一个函数形参参数包Args...args,其中这个形参参数包中可以包含0到任意个模板参数.
template < class ...Args >
void ShouList( Args ...args )
{
    
    }

注意:
上面的参数args前面有省略号,所以它就是一个可变模板参数,我们将带省略号的参数称为"参数包",它里面包含了0到N个(N>>0)个模板参数.

此外,我们也可以在函数模板内中使用sizeof计算函数形参参数包中参数的个数.

template <class ...Args >
void ShouwList( Args... args)
{
    
    
	cout << sizeof...( args ) << endl;
}
int main()
{
    
    
	string str("zh");

	ShouwList('A', str);           //2
	
	ShouwList(1, 'A', str);        //1

}

但是,C++11语法并不支持args[i]这样方式获取可变参数.

//编译无法通过.
template<class ...Args>
void ShowList(Args... args)
{
    
    
		for (int i = 0; i < sizeof...(args); i++)
	{
    
    
		cout << args[i] << " "; //使用args[i]打印参数包中的每个参数
	}
	cout << endl;
}

获取可变模板参数包的值

由于我们无法直接获取函数形参参数包args中的每个参数,只能通过展开参数包的方式获取参数包中的每一个参数,这是可变模板参数的一个主要特点,也是最大的难点.但是,我们可以通过一些特殊的办法来获取函数形参参数包的值.

递归函数方式展开参数包获取

( 1 ) :我们的本意是想通过函数递归的方式,将函数参数包每次调用ShouwList时都分为参数类型T,和参数包(个数-1)的形式,并将此时的函数参数类型T的参数值t打印出来.

( 2 ): 当函数参数包的参数个数为0时,此时与原来的ShouwList函数中的两个形参并不匹配,这时便不能继续递归了.此时,类似与我们以前写递归的结束条件一样,我们需要额外再写一个递归终止函数,随后编译器就会递归调用该函数.

//递归终止函数.
void ShouwList ( )
{
    
    
	cout << t << endl;
}

//利用递归将函数形参参数包展开.
template <class T,class  ...Args >
void ShouwList( T value,Args... args  )
{
    
    
	cout << value << " ";
	ShouwList(args...);
}
int main()
{
    
     
	ShouwList(1);                    //1 
	 
	ShouwList(1, 'A');              // 1, A

	ShouwList(1, 'A', std::string("sort")); // 1, A,sort
}

逗号表达式展开参数包获取

这种展开参数包的方式,不需要通过递归终止函数,而是在expand函数体中展开的.

  • 其中PrintArg是一个处理参数包中每一个参数的函数,每一次处理即对每一种参数t的打印.
  • 我们知道,逗号表达式中会按照顺序执行逗号前面的表达式.并且,expand函数中的逗号表达式:(PrintArg(args),0)也是按照这个顺序执行,先执行PrintArg(args),然后,再得到该一次逗号表达式的结果为0.
  • 同时,该处运用到了C++11的另外一个特性–初始化列表,即通过初始化列表来初始化一个变长的数组,{( PrintArg(args),0)…}将会展开成((PrintArg(args1),0),(PrintArg(args2),0),( PrintArg(args3),0),etc…),这代表每次展开一个逗号表达式args中参数包的个数就减少一个,最终,会创建一个元素值都为0的数组arr[sizeof…(args)].
  • 于是,在使用初始化列表和逗号表达式构造数组的时候,已经默认将args参数包中的参数都展开了.
//将每次的函数形参进行打印.
template < class T > 
void PrintArg(T t)
{
    
    
	cout << t << " ";
}
//利用递归将函数形参参数包展开.
template <class ...Args >
void ShouwList(Args... args)
{
    
    
	int arr[] = {
    
     (PrintArg(args),0)... };
	cout << endl;
}
int main()
{
    
     
	ShouwList(1);                    //1 
	 
	ShouwList(1, 'A');              // 1, A

	ShouwList(1, 'A', std::string("sort")); // 1, A,sort

	return 0;
}

小结:
可变模板参数一般用于需要传递多个形参的函数.

STL容器中的emplace相关接口参数

emplace_back与STL容器中的push_back的主要区别

如果插入实参为int,char等内置类型,那么push_back与emplace_back没有区别.
在这里插入图片描述

可是,如果我们存储的数据类型为自定义类型,例如:pair,区别如下.

  • 使用push_back插入数据时,只允许传递一个实参.
  • 使用emplace_back插入数据时,可以允许传递多个实参.
#include <vector>
int main()
{
    
     
	vector<pair< std::string,int>> v1;

	v1.push_back(make_pair("sort", 1));

	v1.emplace_back("sort", 1);
	
	return 0;
}

emplace_back与push_back的差异原理

  • emplace_back指:当需要传递多个实参时,emplace_back利用将参数包展开的方式提出来,然后直接对vector中pair进行构造.
  • push_back指:先利用传过来的一个实参构造pair,然后再利用构造出来的pair去构造vector中的pair.(如果构造出来的pair为左值,那就是拷贝构造,如果构造的为右值,那就是移动构,具体看vector中的具体实现).
    在这里插入图片描述

emplate_back与push_back的区别验证

在vector中,使用emplace_back和push_back传递多个实参调用时没有区别,都是调用一次构造加一次拷贝构造,这种为特殊情况,具体要看vector中的emplace_back的源码.
在这里插入图片描述

但是对于STL中其他容器来说,例如:list.在相同情况下,emplace_black相比于push_black来说少调用了一次拷贝构造.
在这里插入图片描述

总结:
综合以上情况,我们在插入数据时尽量使用emplace_back,进而更加高效.

猜你喜欢

转载自blog.csdn.net/m0_63300413/article/details/130863026