【C++】C++11(三)

news2024/9/26 1:25:32

我们在C++11(2)中已经很好的解释了右值引用,这次来看看右值引用剩余的一些话题:可变参数包与emplace_back。

目录

  • 可变参数模板:
    • 可变参数的sizeof:
    • 可变参数的展开:
      • 递归函数方式展开参数包:
      • 逗号表达式展开参数包:
  • emplace_back:
    • 对于深拷贝的类:
      • 无区别:
      • 有区别:
    • 对于浅拷贝的类:
      • 无区别 &&有区别:

可变参数模板:

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。
现阶段我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止。

下面就是一个基本可变参数的函数模板:

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

可变参数的sizeof:

我们有时需要知道传入的参数个数,于是我们可以使用sizeof进而得知。

示例:

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

int main()
{
	ShowList(1, "你好", 4);

	return 0;

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

可变参数的展开:

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

递归函数方式展开参数包:

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value <<" ";
	ShowList(args...);
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

具体该怎么理解?
当我们调用函数传参时,第一个模板参数T会自动推演出参数包第一个参数,这样args这个参数包就少了一个参数,并继续调用自己,每次都会少第一个参数,就这样递归的推演直到遇到终止函数(即只剩一个参数时,因为编译器总会自动选择最匹配的函数进行调用)。

逗号表达式展开参数包:

这个比较奇葩。

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。

这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。

expand函数中的逗号表达式:(PrintArg(args), 0),也是按照这个执行顺序,先执行PrintArg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(PrintArg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0), (PrintArg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。

由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包

template <class T>
void PrintArg(T t)
{
	cout << t << " ";	
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

注意:我们的可变模板参数都是在编译时推演出来的具体参数,所以我们也可以结合以下代码更好的理解逗号展开参数包。

void ShowList(char a1, char a2, std::string a3)
{
	int arr[] = { (PrintArg(a1), 0), 
				   (PrintArg(a2), 0),
				   (PrintArg(a3), 0),};
	cout << endl;
}

emplace_back:

在这里插入图片描述
注意到我们的emplace_back也是使用了可变模板参数,但是使用了万能引用,这样大大传值返回的开销。
我们上述进行展开参数包时也都没有使用万能引用。

另外,还有一个说法就是empalce普遍比push系列效率优秀,这又是怎么一回事?

我们来看看:

对于深拷贝的类:

首先对于深拷贝我们还是最好有一个确切的例子才能更好的理解:
下段代码是一个简单实现string的模拟类

namespace cyc
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			cout << "string(char* str)" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
			:_str(nullptr)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		// s1 = 将亡值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

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

		string operator+(char ch)
		{
			string tmp(*this);
			tmp += ch;
			return tmp;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

无区别:

当我们创建一个list时,尾插一个string对象这是无区别的,原因在于

emplace时如果直接提供目标对象,则会调用拷贝构造或者移动语义进行资源的创建或者转移,在这里由于传递的是左值,因此会构造新的对象

	std::list<cyc::string> lt;
	// 深拷贝:
	// 无区别
	cyc::string s1("111");
	lt.push_back(s1);   // 拷贝构造
	lt.emplace_back(s1);// 拷贝构造

在这里插入图片描述
对于此的结论:两者都是深拷贝,无区别。
我们再来传入右值试一试:
在这里插入图片描述
可以看到仍没有区别,两者都是调用移动语义。

有区别:

当传入的都是一个常量字符串时呢?

emplace时当传入的是一个目标对象所需的构造参数时,会在最后调用一个定位new,直接构造.

在这里插入图片描述

在这里插入图片描述

对于浅拷贝的类:

对于深拷贝来说优化不是特别明显,但是对于浅拷贝来说,且很大的一个类时,emplace的性能就很棒了。

我们假设有一个日期类。

无区别 &&有区别:

	// 浅拷贝的类
	// 没区别
	std::list<Date> list2;
	Date d1(2023, 5, 28);
	list2.push_back(d1);
	list2.emplace_back(d1);

	cout << "-----------------------------------------" << endl;

	Date d2(2023, 5, 28);
	list2.push_back(move(d1));
	list2.emplace_back(move(d2));

	// 有区别
	cout << "-----------------------------------------" << endl;

	list2.push_back(Date(2023, 5, 28));
	list2.push_back({ 2023, 5, 28 });

	cout << "-----------------------------------------" << endl;
	list2.emplace_back(Date(2023, 5, 28)); // 构造+移动构造
	list2.emplace_back(2023, 5, 28);       // 直接构造

注意:这里对于可变参数模板传参有一个特点,不能使用initializer_list进行传参,因为本质上初始化列表是一个伪容器,除了迭代器就没有方法得到其中的元素了,而我们的参数包想要的是一个一个分离的参数,initializer_list会被认为是一个整体,编译器不能正确的将其解析为一个一个元素,故不支持。
而vector等STL容器就不一样了,他们是真正的容器。

虽然技术上可以实现,但是为了哲学性,严谨性等原因还是没有这样做。

完~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1914661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端JS特效第27波:jQuery商品放大镜预览代码

jQuery商品放大镜预览代码&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下&#xff1a; <!doctype html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content&quo…

【数智化案例展】厦门市信息中心——爱数助力厦门政务云构建两地三中心多级数据灾备体系...

‍ 爱数案例 本项目案例由爱数投递并参与数据猿与上海大数据联盟联合推出的《2024中国数智化转型升级创新服务企业》榜单/奖项”评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 厦门市信息中心是厦门市电子政务专门机构&#xff0c;加挂厦门市电子政务中心、厦门市大数…

拆分盘究竟是什么?一篇文章带你了解!

拆分盘是一种特殊的理财产品或投资模式&#xff0c;它通常被描述为“只涨不跌”的投资方式&#xff0c;多指股票&#xff0c;但实质上与传统股市中的股票有本质区别。以下是对拆分盘的详细解析&#xff1a; 一、拆分盘的定义 拆分盘可以理解为一种只涨不跌的理财股票。其特点在…

1996-2023年各省农村居民人均消费支出数据(无缺失)

1996-2023年各省农村居民人均消费支出数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;1996-2023年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;农村居民人均消费支出 4、范围&#xff1a;31省 5、缺失情况&#xff1a;无缺失 6、指标解释&…

HTTP中常见的状态码有哪些?

常用的包括以下几个&#xff1a; 200&#xff1a;表示客户端请求成功 201&#xff1a;请求成功,服务器创建了新资源。 204&#xff1a;无内容&#xff0c;服务器成功处理请求&#xff0c;但未返回任何内容。 206: 表示“部分内容”,当客户端请求一个资源的一部分时&#xff0c;…

如何利用AI自动生成绘画?5款AI绘画的六大神器!

以下是五款专业级别的AI绘画工具&#xff0c;它们能够帮助用户迅速生成高质量的AI艺术作品&#xff1a; 1.AI先行者&#xff1a; 这是一款流行的 AI 绘画平台&#xff0c;它利用深度学习技术将你的照片或图像转换成艺术风格的绘画作品。你可以在线使用上上传图片并选择喜欢的艺…

微信定时推送LeetCode每日一题,再也不怕没人喊你刷题了

前段时间发过一篇关于微信机器人开发的文章&#xff0c;讲述了如何快速开发一个微信机器人&#xff0c;本篇文章就来实现一个最近开发的一个功能案例&#xff0c;在这个案例中会遇到了各种问题&#xff0c;可以帮助大家减少自己去踩坑的时间。通过此案例也可以帮助你去扩想一些…

OpenCV中使用Canny算法在图像中查找边缘

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 算法描述 Canny算法是一种广泛应用于计算机视觉和图像处理领域中的边缘检测算法。它由John F. Canny在1986年提出&#xff0c;旨在寻找给定噪声条件下的最佳边…

storybook中剔除chakra-ui的影响,或者剔除其他ui包的影响

介绍 经过一系列初始化完成后&#xff0c;storybook项目启动出来发现多余了一个ui框架的内容。如下图 因为项目中仅仅使用chakraUI的一些功能&#xff0c;并没有使用整体组件功能&#xff0c;所以说完全没必要把它留着这里。经过排查可以使用storybook中的refs功能剔除掉不需要…

1996-2023年各省农业总产值数据(无缺失)

1996-2023年各省农业总产值数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;1996-2023年 2、来源&#xff1a;国家统计局、各省年鉴 3、指标&#xff1a;农业总产值 4、范围&#xff1a;31省 5、缺失情况&#xff1a;无缺失 6、指标解释&#xff1a;农业总产值是…

多协议网关设计架构与实现,支持 RS485/232、CAN、M-Bus、MQTT、TCP 等工业协议接入(附代码示例)

一、项目概述 1.1 背景 随着物联网技术的快速发展&#xff0c;越来越多的设备需要接入网络进行数据交互。然而&#xff0c;不同设备往往采用不同的通信协议&#xff0c;例如工业现场常用的Modbus、CAN、电力载波等&#xff0c;以及物联网领域常用的MQTT、TCP/IP等&#xff0c…

推荐一款功能强大的 GPT 学术优化开源项目GPT Academic:学术研究的智能助手

今天&#xff0c;我将向大家介绍一个强大的开源项目—GPT Academic&#xff0c;它或许正是你一直在寻找的理想工具。 已一跃成为 60.4k Star 的热门项目 GPT Academic 目前在 GitHub 上已经揽获了 60.4k 的 Star&#xff0c;这不仅反映了它的受欢迎程度&#xff0c;更证明了它…

什么是光储充一体化? 光储充一体化有什么优势?

大部分省份划定配储的比例不低于10% “光储充一体化”政策文件:国家层面政策名称 政策要点 发布时间 发布单位 结合实际建设光伏发电、储能、充换电一体化的充电基础设施。中央财政将安排奖励资金支持试点县开展试点工作&#xff0c;示范期内&#xff0c;每年均达到最高目标的试…

(pyqt5)弹窗-Token验证

前言 为了保护自己的工作成果,控制在合理的范围内使用,设计一个用于Token验证的弹窗. 代码 class TokenDialog(QDialog):def __init__(self, parentNone, login_userNone, mac_addrNone, funcNone):super(TokenDialog, self).__init__(parent)self.login_user login_userself…

2024年网络监控软件排名|10大网络监控软件是哪些

网络安全&#xff0c;小到关系到企业的生死存亡&#xff0c;大到关系到国家的生死存亡。 因此网络安全刻不容缓&#xff0c;在这里推荐网络监控软件。 2024年这10款软件火爆监控市场。 1.安企神软件&#xff1a; 7天免费试用https://work.weixin.qq.com/ca/cawcde06a33907e6…

d3dcompiler_43.dll文件是什么?如何快速有效的解决d3dcompiler_43.dll文件丢失问题

dcompiler_43.dll 是一个Windows系统中的系统文件&#xff0c;属于DirectX软件的一部分。这个dcompiler_43.dll&#xff08;动态链接库&#xff09;文件主要用于处理与3D图形编程有关的任务&#xff0c;是运行许多游戏和高级图形程序必需的组件之一。那么如果电脑丢失d3dcompil…

新手如何配置运行yolov5环境

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️如遇文章付费&#xff0c;可先看…

部署Harbor仓库

本章内容&#xff1a; 安装docker-ce部署harbor仓库上传和拉取 1.安装docker 1&#xff09;拉取源码 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 2&#xff09;安装docker-ce yum -y install docker-ce 3&#…

Java 8革新:现代编程的全新标准与挑战

文章目录 一、方法引用二、接口默认方法三、接口静态方法四、集合遍历forEach()方法 一、方法引用 方法引用是Java 8中一种简化Lambda表达式的方式&#xff0c;通过直接引用现有方法来代替Lambda表达式。 方法引用使得代码更加简洁和易读&#xff0c;特别是在处理函数式接口时&…

数学建模国赛入门指南

文章目录 认识数学建模及国赛认识数学建模什么是数学建模&#xff1f;数学建模比赛 国赛参赛规则、评奖原则如何评省、国奖评奖规则如何才能获奖 国赛赛题分类及选题技巧国赛赛题特点赛题分类 国赛历年题型及优秀论文数学建模分工技巧数模必备软件数模资料文献数据收集资料收集…