C++11_完美转发与模板可变参数(std::forward<>,template <class ...Args>,push_back与emplace_back)

本文探讨了C++中的右值引用及其在函数参数传递时的退化问题,通过完美转发std::forward解决。接着介绍了模板的可变参数,展示了两种展开参数包的方法。最后讲解了STL中emplace_back的用法,特别是如何利用模板和完美转发在列表末尾高效地插入元素。通过实例展示了emplace_back如何避免不必要的拷贝和移动,提高性能。
部署运行你感兴趣的模型镜像


C++右值引用与移动语义

1.完美引用(std::forward<>)

在引入完美转发前先分析以下代码

#include<iostream>

using std::cout; using std::endl;

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<class T>
void Perfect(T&& t)
{
	Fun(t);
}

int main()
{
	Perfect(1);//右值
	int a = 5;
	Perfect(a);//左值
	Perfect(std::move(a));//右值
	const int b = 10;
	Perfect(b);//const 左值
	Perfect(std::move(b));//const右值
	return 0;
}

注意:
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力。

代码运行结果如下
在这里插入图片描述
根据运行结果可以推测:
右值在函数传参的时候丢失了右值属性,变成了左值

右值传参时退化为左值原因

左值与右值最明显的区别是:右值不可以取地址,左值可以
右值在传参的时候被保存到了特定的位置,所以就可以取地址了,失去了右值属性。
eg:如上代码,右值被保存到了模板参数t中,可以取地址

改进方法,使用std::forward<>来保证右值属性的传递(完美转发)

#include<iostream>

using std::cout; using std::endl;

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<class T>
void Perfect(T&& t)
{
	Fun(std::forward<T>(t));
}

int main()
{
	Perfect(1);
	int a = 5;
	Perfect(a);
	Perfect(std::move(a));
	const int b = 10;
	Perfect(b);
	Perfect(std::move(b));
	return 0;
}

运行结果:
在这里插入图片描述
综上可知:只要右值传参,就一定要使用完美转发

2.模板的可变参数

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

Args:是模板参数包。
args:函数形参参数包
声明Args… args这个参数包中可能有0-任意个参数

方法一递归展开参数包的方法如下:

#include<iostream>

using std::cout; using std::endl;

void FunctionArg() { cout << endl; }

template <class T,class ...Args>
void FunctionArg(T value,Args... args)
{
	cout << "参数个数为:" << sizeof...(args) << endl;
	cout << value << endl;
	FunctionArg(args...);
}

template <class ...Args>
void Function(Args... args)
{
	FunctionArg(args...);
}

int main()
{
	Function(std::string("sort"), 1, 'A');
	Function(1, 3, 2.34);
}

运行结果:
在这里插入图片描述
分析:
Function函数中调用FunctionArg函数
在FunctionArg函数中,参数包被展开成value+args。
这样逐层递归,最后参数包args没有参数时要退出递归。所以上述函数要提供一个无参的函数。

注意:模板可变参数并不支持Args[i]来找参数
同时模板在展开参数时也不支持if语句。
因为if与else是逻辑代码,编译器在展开参数包的时是编译过程,模板在推演类型。同理也可以解释Args[i]型找参数

方法二:逗号表达式展开参数包(列表初始化)

在这里插入图片描述
如上图,编译器在编译时会计算数组的大小,参数包如果有4个参数,数组大小就为4。

如果参数都是整形,数组内的元素就是参数。

在这里插入图片描述
C++数组中元素类型相同,为了保证参数包参数类型结果都是整形,这里使用了逗号表达式。
eg:(“abc”,1)这个表达式最后结果为整形,综上,上述代码改进为

#include<iostream>

using std::cout; using std::endl;

template<class T>
void Print(T t)
{
	cout << t << " ";
}

void Function() { cout << endl; }//匹配0个参数的情况

template <class ...Args>
void Function(Args... args)
{
	//列表初始化
	int arr[] = { (Print(args),0)... };
}

int main()
{
	Function();
	Function(1, 2, 3, 4);
	cout << endl;
	Function(1, "Hello", 3.14);
}

如上图列表初始化会将参数包展开为:
{(Print(args), 0)…}将会展开成((Print(arg1),0), (Print(arg2),0), (Print(arg3),0), etc… )}
这些表达式最后的结果都是整形0,可以放到数组中

同理,还可以

#include<iostream>

using std::cout; using std::endl;

template<class T>
int Print(T t)
{
	cout << t << " ";
	return 0;
}

void Function() { cout << endl; }

template <class ...Args>
void Function(Args... args)
{
	//列表初始化
	int arr[] = { Print(args)... };
}

int main()
{
	Function();
	Function(1, 2, 3, 4);
	cout << endl;
	Function(1, "Hello", 3.14);
}

3.C++STL中emplace_back)

以list中的emplace_back举例子
在这里插入图片描述
注意这里的void emplace_back (Args&&... args);中&&是万能引用,可以引用左值或右值。
在列表的末尾插入一个新元素。这个新元素可以使用参数包作为其构造的参数来构造。

构造时使用std::allocator_traits::construct来构造
在这里插入图片描述
std::allocator_traits::construct可以理解为C++定位new。

emplace_back的使用

#include<iostream>
#include<list>

using namespace std;

class Date
{
private:
	int year; int month; int date;
public:
	Date(int _year = 1, int _month = 1, int _date = 1) 
		:year(_year), month(_month), date(_date)
	{}
};



 int main()
{
	std::list<Date> lt;
	Date d1 = { 2022, 2, 2 };
	lt.push_back(d1);
	lt.push_back(Date(2022,2,2));
	lt.push_back({2022,2,2});//列表初始化

	lt.emplace_back(d1);
	lt.emplace_back(Date(2022, 2, 2));
	lt.emplace_back(2022, 2, 2);


	return 0;
}

首先,emplace_back是可变参数的万能引用。拿到参数包时调用construct,调用时采用完美转发保证参数包内参数属性
在这里插入图片描述
综上:
在这里插入图片描述

测试emplace_back

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<list>

using namespace std;

namespace NUC
{
	class string
	{
	public:
		string(const char* str = "")
			:size(strlen(str))
			, capacity(size)
		{
			cout << "构造函数" << endl;

			_src = new char[capacity + 1];
			strcpy(_src, str);
		}
		~string()
		{
			delete[] _src;
			_src = nullptr;
		}
		string(string&& s)
			:_src(nullptr), size(0), capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			this->swap(s);
		}
		void swap(string& s)
		{
			::swap(_src, s._src);//全局swap  std::swap
			::swap(size, s.size);
			::swap(capacity, s.capacity);
		}
		string(const string& s)
			:_src(nullptr)
		{
			cout << "拷贝构造函数" << endl;
			string tmp(s._src);
			swap(tmp);
		}
		string& operator=(const string& s)
		{
			cout << "operator=" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		char* begin() { return _src; }
		char* end() { return _src + size; }

		void reserve(size_t i)
		{
			if (i > capacity)
			{
				char* tmp = new char[i + 1];
				strncpy(tmp, _src, size);
				delete[]_src;
				_src = tmp;
				capacity = i;
			}
		}
		void push_back(char ch)
		{
			if (size == capacity)
			{
				size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
				reserve(newcapacity * 2);
			}
			_src[size] = ch;
			_src[size + 1] = '\0';
			size++;
		}

	private:
		char* _src;
		int size;
		int capacity;
	};
}


 int main()
{
	list<std::pair<int,NUC::string>>lt;
	pair<int, NUC::string>s(1, "Hello");
	lt.emplace_back(s);//调用构造,再调用拷贝构造
	lt.emplace_back(pair<int, NUC::string>(1, "Word"));//调用构造函数,再调用移动构造
	lt.emplace_back(1,"hello");//直接调用构造函数初始化


	return 0;
}

运行结果为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

4. 可变参数模板 4.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...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板 ⼀样,每个参数实例化时遵循引⽤折叠规则。 • 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。4.2 包扩展 比特就业课 • 对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个 包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元 素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。底层 的实现细节如图1所⽰。 • C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。4.3 empalce系列接⼝ • template <class... Args> void emplace_back (Args&&... args); • template <class... Args> iterator emplace (const_iterator position, Args&&... args); • C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上 兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container<T>,empalce还⽀持 直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。 • emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列 • 第⼆个程序中我们模拟实现了list的emplaceemplace_back接⼝,这⾥把参数包不段往下传递, 最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前⾯说的empalce⽀持 直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。 • 传递参数包过程中,如果是 Args&&... args 的参数包,要⽤完美转发参数包,⽅式如下 std::forward<Args>(args)... ,否则编译时包扩展后右值引⽤变量表达式就变成了左 值.想复习下可变参数模板
07-15
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BaiRong-NUC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值