新C++(16):可变参数模板

        新C++(16):可变参数模板_第1张图片"我看着,天真的我自己。" 


        

        我想,任何一位C语言初始者的第一行代码,都离不开一个函数——printf。

printf("hello world!");

        如果我们想要打印一个无符号整形、字符型或者float类型,我们通常会写出如下的代码,

unsigned int a = 1;
char ch = 'A';
float d = 1.1f;
printf("无符号整型:%u",a);
printf("字符型:%c",ch)
//....
printf("无符号整型:%u 字符型:%c float类型:%f",a,ch,d);

        我们会发现,当我们使用printf函数时,它即可以接受0个参数,也可以接收1、2、3个参数。这显然激发了作为初学者我们的好奇心。于是乎,我们只得去查查这个printf背后有什么神秘的讲究。

新C++(16):可变参数模板_第2张图片

         不难发现,让printf能够接收多参数的秘密,一定出现在这三个点上,而这三个点是什么呢?用术语来说,叫做"可变参数"。它的功能即,可以接收0~N个参数

---前言


一、可变参数模板

(1)可变参数模板语法

        C++在兼容C的基础上,引入了函数模板和类模板。但是在C++11之前,函数模板和类模板的参数是固定的,C++11的新特性可变参数模板能够让您创建可以接受可变参数。

template
void ShowList(Args... args)
{}

  Args是一个 "模板参数包" 而args是一个 "函数参数包"。声明一个参数包 "Args...args",这个参数包中可以包含0到任意个模板参数。

  上面的参数args前面有省略号,所以它就是一个可变模版参数。

(2)模板参数取值参数

递归函数方式展开;

void ShowList()
{
	cout << endl;
}

template
void ShowList(T val,Args... args)
{
	cout << val << " ";
	ShowList(args...);
}

新C++(16):可变参数模板_第3张图片

 

逗号表达式展开参数包;

template 
void PrintArg(T& t)
{
	cout << t << " ";
}

template
void ShowList(Args... args)
{
	int a[] = { (PrintArg(args),0)... };
	cout << endl;
}

PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。我们知道逗号表达式会按顺序执行逗号前面的表达式。同时还用到了C++11的另外一个特性——"初始化列表",通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}。让编译器自己展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... )

新C++(16):可变参数模板_第4张图片

  

        当然它是如何知道自己的可变参数包有几个参数的呢?

新C++(16):可变参数模板_第5张图片

 


        这几套操作下来,可能熟悉C++或者不熟悉C++的都写满了一脸的问号?这都是些个啥?不过好在,在实际中,我们仅仅作为小小的计算机行业的一员,少有使用到这些恼人的语法。

二、emplace_back vs push_back 

        但其实,我们引入前面的可变参数模板,仅仅是为了讲本小段做的铺垫。C++11为很容器引入emplace的插入版本。于是乎,有人就说,“要多使用emplace系列的插入版本,因为它比push或者insert系列的插入版本效率要高"。

        但事实上真是如此嘛?当然,口说无凭。

vector:

新C++(16):可变参数模板_第6张图片

map:
新C++(16):可变参数模板_第7张图片 

emplace难道插入效率真的最好吗?

内置类型:
新C++(16):可变参数模板_第8张图片        对于内置类型而言,emplace_back是直接拿参数去构造里面的节点值。而此时用push_back,不管是make_pair 还是 initializer_list都是先构造这些对象,然后再用这些对象去拷贝构造节点内容。但是这些拷贝都是浅拷贝,代价挺小的。唯一emplace_back的有点就在于,少进了一次拷贝构造。

自定义类型:
新C++(16):可变参数模板_第9张图片        从上面的图我们可以得出以下二个 结论:

对于左值而言:

        不管是emplace系列还是 push插入系列,都只能调用"深拷贝"。完成对结点的初始化。

对于右值而言:

        emplace系列能直接去拿去参数构造。而相对于push系列,就会多一步"移动构造",但是移动构造都是一些浅拷贝,代价相反不是很大。但是,如果一个类没有提供"移动构造"或者“移动赋值”,那么此时无脑用emplace系列插入接口是完完全全正确的,且效率很高的。


总结:

① 可变参数模板的语法是什么?它可以接收0~N个参数。

② 对于内置类型而言,emplace系列会比pushi\insert系列少 调用一次拷贝构造。

③ emplace系列与pushi\insert系列对于左值属性的对象并没有什么大的区别。但是对于具有右值属性的值,如果该类没有提供"移动构造或者移动赋值",此时无脑emplace是可取的。

本篇就到此结束了,感谢你的阅读。

祝你好运,向阳而生~

 

新C++(16):可变参数模板_第10张图片

你可能感兴趣的:(新C++,c++)