【C++】C++11——可变参数模板和emplace

2023-09-22 15:12:45


可变参数模板是C++11新增的最强大的特性之一,它对参数高度泛化,能够让我们创建可以接受可变参数的函数模板和类模板。

  • 在C++11之前,类模板和函数模板中只能包含固定数量的模板参数,可变模板参数无疑是一个巨大的改进,但由于可变参数模板比较抽象,因此使用起来需要一定的技巧。
  • 在C++11之前其实也有可变参数的概念,比如printf函数就能够接收任意多个参数,但这是函数参数的可变参数,并不是模板的可变参数。

可变参数模板的定义方式

可变参数包的个数可以为0,也可以为N

template<class ...Args>
返回类型 函数名(Args ...args)
{
	//函数体
}

如:

template<class ...Args>
void ShowList(Args... args)
{}
  • 模板参数包Args和函数形参参数包args名字可以任意指定,并不是强制的。

可变参数模板的传值

int main()
{
	ShowList();
	ShowList(1);
	ShowList(1,2);
	ShowList(1,2,"abc");
	return 0;
}

在可变参数模板中,我们可以传入任意类型

计算可变参数模板参数个数

template<class ...Args>
void ShowList(Args ...args)
{
	cout << sizeof...(args) << endl;//注意...的位置
}

int main()
{
	ShowList();
	ShowList(1);
	ShowList(1,2);
	ShowList(1,2,"abc");
	return 0;
}
  • 在可变参数模板中,一定要注意…的位置问题

在这里插入图片描述

参数包展开方式

递归展开参数包

使用递归方式展开参数包我们可以:

  • 给函数模板再添加一个模板参数,用于接受分离参数包中的首个参数。
  • 在函数模板中调用该模板函数,实参一直调用剩下的参数包
  • 如此递归下去,每次分离出参数包中的一个参数,直到参数包中的所有参数都被取出来。

综合以上的考虑方向,我们首先考虑到的就是当传入0个参数时怎么办?
这时需要我们设置一个专门接受0个参数的函数,来起到终止的作用

void ShowList()
{
	cout << "起到了终止的作用" << endl;
}

template<class T, class ...Args >
void ShowList(T value,Args ...args)
{
	cout << value << endl;
	ShowList(args...);
}

int main()
{
	ShowList();
	ShowList(1);
	ShowList(1,2);
	ShowList(1,2,"abc");
	return 0;
}

在这里插入图片描述

在这里插入图片描述
如果想要无论外部调用时传入多少个参数,最终匹配到的都是同一个函数了时,我们需要这样修改

//递归终止函数
void ShowListArg()
{
	cout << endl;
}
//展开函数
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{
	cout << value << " "; //打印传入的若干参数中的第一个参数
	ShowListArg(args...); //将剩下参数继续向下传
}
//供外部调用的函数
template<class ...Args>
void ShowList(Args... args)
{
	ShowListArg(args...);
}

也可以编写带参的退出函数,这样一来吗,当参数包剩下一个参数的时候,就会自动匹配退出函数

//递归终止函数
template<class T>
void ShowListArg(const T& t)
{
	cout << t << endl;
}
//展开函数
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{
	cout << value << " "; //打印传入的若干参数中的第一个参数
	ShowList(args...);    //将剩下参数继续向下传
}
//供外部调用的函数
template<class ...Args>
void ShowList(Args... args)
{
	ShowListArg(args...);
}

但该方法有一个弊端就是,我们在调用ShowList函数时必须至少传入一个参数,否则就会报错。因为此时无论是调用递归终止函数还是展开函数,都需要至少传入一个参数。

逗号表达式展开参数包

我们可以用一个数组来接收参数包。例如以下方式:

int a[] = {1,2,3};//列表初始化方式

我们可以通过调用参数包,将参数包里的内容通过列表初始化的方式放入数组中。如下
在这里插入图片描述
但是以上代码有一个问题,我们传入的参数必须为整型才配用这个逻辑,怎么解决这个问题呢?

逗号表达式解决

  • 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值。
  • 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式。

这样一来,在执行逗号表达式时就会先调用处理函数处理对应的参数,然后再将逗号表达式中的最后一个整型值作为返回值来初始化整型数组。比如
在这里插入图片描述
实际上我们也可以不用逗号表达式,因为这里的问题就是初始化整型数组时必须用整数,那我们可以将处理函数的返回值设置为整型,然后用这个返回值去初始化整型数组也是可以的。比如:

//支持无参调用
void ShowList()
{
	cout << endl;
}
//处理函数
template<class T>
int PrintArg(const T& t)
{
	cout << t << " ";
	return 0;
}
//展开函数
template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... }; //列表初始化
	cout << endl;
}

emplace插入

C++11标准给STL中的容器增加emplace版本的插入接口,比如list容器的push_front、push_back和insert函数,都增加了对应的emplace_front、emplace_back和emplace函数。如下:
在这里插入图片描述

emplace:接口的意义:

  • emplace插入和普通的插入虽然都支持插入左值和右值,但是emplace还可以插入参数包,但是他不可以使用列表初始化,这是他和其他插入函数的区别。
  • 使用参数包可以有效的解决减少一次拷贝
  • emplace系列接口真正高效的情况是传入参数包的时候,直接通过参数包构造出对象,避免了中途的一次拷贝。

例如:

namespace test
{
	class string
	{
	public:
		string(const char* str)
			:_size(strlen(str))
			, _capacity(_size)
			,_str(new char[_size + 1])
		{
			strcpy(_str, str);
			cout << "string(const char* str) -- 构造函数" << endl;
		}

		//交换两个对象的数据
		void swap(string& s)
		{
			//调用库里的swap
			::swap(_str, s._str); //交换两个对象的C字符串
			::swap(_size, s._size); //交换两个对象的大小
			::swap(_capacity, s._capacity); //交换两个对象的容量
		}
		
		//拷贝构造函数(现代写法)
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 拷贝构造" << endl;

			string tmp(s._str); //调用构造函数,构造出一个C字符串为s._str的对象
			swap(tmp); //交换这两个对象
		}

		//移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 移动构造" << endl;
			swap(s); //交换这两个对象
		}

		//拷贝赋值函数(现代写法)
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;

			string tmp(s); //用s拷贝构造出对象tmp
			swap(tmp); //交换这两个对象
			return *this; //返回左值(支持连续赋值)
		}
		//移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		//析构函数
		~string()
		{
			//delete[] _str;  //释放_str指向的空间
			_str = nullptr; //及时置空,防止非法访问
			_size = 0;      //大小置0
			_capacity = 0;  //容量置0
		}

	private:
		size_t _size;
		size_t _capacity;
		char* _str;

	};
}

int main()
{
	list<pair<int, test::string>> mylist;
	pair<int, test::string> kv(1, "one");
	mylist.emplace_back(kv);
	cout << endl;

	mylist.emplace_back(pair<int, test::string>(2, "two"));
	cout << endl;

	mylist.emplace_back(2, "two");
	cout << endl;
}

在这里插入图片描述

int main()
{
	list<pair<int, test::string>> mylist;
	pair<int, test::string> kv(1, "one");
	mylist.push_back(kv);
	cout << endl;

	mylist.push_back(pair<int, test::string>(2, "two"));
	cout << endl;

	mylist.push_back({ 2, "two" });
	cout << endl;
}

在这里插入图片描述
两者最明显的区别就是一个支持参数包传,一个支持列表初始化传,但是站在内存角度来说emplace跟省空间,emplace可以无脑使用

总结一下:

  • 传入左值对象,需要调用构造函数+拷贝构造函数。
  • 传入右值对象,需要调用构造函数+移动构造函数。
  • 传入参数包,只需要调用构造函数。
更多推荐

pg常用插件

pg软件包自带插件前言pg的插件是基于库的;pg的数据字典介绍:1、pg_stat_statements插件Pg_stat_statements是一个扩展,而不是核心数据库的一部分。它是一个contrib扩展,随postgres源代码一起提供。pg_stat_statements的功能位于一个名为pg_stat_sta

项目进度管理有哪些方法?项目管理中的进度管理

项目进度管理是项目实施过程中,根据制定的计划对各阶段的任务和项目最终完成的期限所进行的管理。在执行该计划的过程中,检查实际进度是否按计划要求进行,若出现偏差,便要及时找出原因,采取必要的措施或调整,直至项目完成。保证项目能在满足其时间约束条件的前提下实现总体目标。管事:项目进度管理主要涉及到对项目的任务进行分解,并设计

uniapp 使用subNVue原生子窗体显示弹框或悬浮框

效果展示在uniapp中,我们可以使用subNVue原生子窗体来解决web-view等原生页面中弹框无法显示的问题。subNVue原生子窗体是uniapp提供的一种原生组件,可以在uniapp中嵌入原生页面,并且可以与uniapp页面进行通信。我们可以在原生页面中使用uniapp提供的API来与uniapp页面进行通信

【开发篇】二、属性绑定与校验

文章目录1、@ConfigurationProperties自定义Bean属性绑定2、@EnableConfigurationProperties注解3、@ConfigurationProperties第三方Bean属性绑定4、松散绑定5、常用计量单位6、数据校验7、yaml绑定值的坑--关于进制1、@Configur

玩转 gpgpu sim 02记 —— 构建了什么

1.设置环境变量编译gpgpu-sim需要先运行脚本setup_environment,sourcesetup_environment,注释如下,主要是设置一些Makefile中会用到的环境变量#seeREADMEbeforerunningthis#下面这句用来检测当前的shell环境是不是bash或者sh或者zsh,

Jtti:新加坡云服务器怎么部署javaweb

在新加坡云服务器上部署JavaWeb应用程序需要执行以下步骤:1.准备云服务器:首先,您需要租用或创建一个新加坡地区的云服务器,确保服务器的操作系统和硬件资源满足您的需求。2.安装Java环境:确保您的服务器上已经安装了Java开发环境(JDK)。您可以使用以下命令来检查是否已经安装:java-version如果未安装

乖离率BIAS指标选股公式,判断多空力量和超买超卖

乖离率(BIAS)指标用于衡量股价与移动平均线之间的偏离程度,可以用来判断当前市场的多空力量和超买超卖情况。乖离率的计算公式比较简单,如下:BIAS=(收盘价-N日移动平均价)/N日移动平均价×100其中,N代表选择的时间周期,比如5日、10日或20日等。从公式可以看出乖离率BIAS的正负值表示股价相对于均线的偏离方向

思腾云计算

近日,IDC发布了《2022年H2中国加速计算市场分析报告》。报告显示,2022年加速服务器市场同比增长22.9%。中国加速服务器市场预计将在未来五年内保持稳定增长。IDC预测,到2027年,中国加速发展的服务器市场将达到163亿美元。思腾合力经过近几年的快速发展,已逐步成长为国内人工智能服务器领域领先企业。本次报告榜

传统企业如何实现数字化转型?

传统企业实现数字化转型是一个复杂且多方面的过程,涉及将数字技术和战略融入业务的各个方面,以推动创新、效率和竞争力。以下是传统企业实现数字化转型可以遵循的步骤和策略:1.领导层的认可和愿景:首先要确保最高领导层(包括首席执行官和董事会)的承诺。他们应该了解数字化转型的重要性并愿意对其进行投资。就数字化转型对组织的意义制定

Python实现猎人猎物优化算法(HPO)优化LightGBM回归模型(LGBMRegressor算法)项目实战

说明:这是一个机器学习实战项目(附带数据+代码+文档+视频讲解),如需数据+代码+文档+视频讲解可以直接到文章最后获取。1.项目背景猎人猎物优化搜索算法(Hunter–preyoptimizer,HPO)是由Naruei&Keynia于2022年提出的一种最新的优化搜索算法。受到捕食动物(如狮子、豹子和狼)和猎物(如雄

在混合云中优化边缘计算的三种方法

回答这些关键问题,以确保在部署边缘计算时获得更大的价值和更好的结果。企业通过混合云部署分散计算资源的努力揭示了一种独立但相关的策略:边缘计算的使用,在这种策略中,组织利用远程位置或托管设施的本地数据中心资源。两个通用原则定义了边缘计算。首先,它是分布式的,计算和处理在远离集中式数据中心或云的地方进行。其次,它是特定于位

热文推荐