C++11(四)

目录

包装器

function包装器

bind绑定 

更改实参传递的顺序和实参传递的个数

线程库 


本期我们将继续进行C++11新特性的学习。

包装器

function包装器

function包装器,我们也称之为适配器,本质上就是一个类模板,为什么要引入function包装器的概念呢?

观察下述代码。

f(1);

这个代码站在C语言的角度没什么说的就是一个普通的函数调用,但是学了这么长时间的C++,我们能清楚地意识到这绝不可能单单是一个函数的调用,这个f可以为多种可调用对象,最简单的就是f为函数指针(函数名),也就是函数调用,但是当我们学习了仿函数和lambda表达式之后,这个f可以是仿函数对象,也可以是lambda表达式对象。

参考下述代码。

#include<iostream>


template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	std::cout << "count:" << ++count << std::endl;
	std::cout << "count:" << &count << std::endl;

	return f(x);
}
//调用的是拷贝构造
double f(double i)
{
	return i / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	// 函数名
	std::cout << useF(f, 11.00) << std::endl;

	// 函数对象
	std::cout << useF(Functor(), 11.00) << std::endl;

	// lambda表达式
	std::cout << useF([](double d)->double{ return d / 4; }, 11.00) << std::endl;

	return 0;
}

运行结果如下。

不难发现,这个函数中的全局变量count我们实例化成了3份,因为count变量的地址有三份,也就意味着当我们给useF这个函数传递不同的值时,这个useF函数本质上是被实例化成了三份,因为F这个木类模板被实例化成了三份。 

实例化成三份,资源消耗太大,有没有其它的办法使得传递不同的参数时,最终函数只实例化一份呢?

此时,function包装器就派上了用场。

我们先通过一段代码了解一下function包装器如何使用,代码如下。

int f(int a, int b)
{
	return a + b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a * b;
	}
};

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b + 1;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}
};

int main()
{
	//1.传递函数函数指针
	function<int(int, int)>f1 = f;
	cout << f1(1, 2) << endl;

	//2.传递仿函数对象
	function<int(int, int)>f2 = Functor();
	cout << f2(1, 2) << endl;

	//3.传递非静态成员函数函数指针
	function<double(Plus, double, double)>f3 = &Plus::plusd;
	cout << f3(Plus(), 1.1, 2.2) << endl;

	//4.传递静态成员函数函数指针
	function<int(int, int)>f4 = &Plus::plusi;
	cout << f4(1, 3) << endl;

	//5.传递lambda表达式
	function<int(int, int)>f5 = [](int a, int b) {return (a + b) * 10; };
	cout << f5(1, 2) << endl;
	return 0;

}

运行结果如下。

其实function就是一个类模板,其创建的对象可以接收可调用对象(函数指针,仿函数,lambda表达式)。

 熟悉了function的用法之后,我们再对上述实例化成三份useF函数的情景做出优化。

我们使用function进行代码改造,代码如下。

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	std::cout << "count:" << ++count << std::endl;
	std::cout << "count:" << &count << std::endl;

	return f(x);
}
//调用的是拷贝构造
double f(double i)
{
	return i / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	// 函数名
	function<double(double)> f1 = f;
	std::cout << useF(f1, 11.00) << std::endl;

	// 函数对象
	function<double(double)>f2 = Functor();
	std::cout << useF(f2, 11.00) << std::endl;

	// lamber表达式
	function<double(double)>f3 = [](double d)->double { return d / 4; };
	std::cout << useF(f3,11.00) << std::endl;

	return 0;
}

 运行结果如下。

不难发现,最终useF函数只实例化了一份,因为count的地址都是一样的。这便是function包装器的用法,提升了代码的效率,节省了资源。

bind绑定 

bind是一个函数模板,我们也称之为函数包装器(适配器),用于接收一个可调用对象,生成一个新的可调用对象,适配原对象的参数列表。

更改实参传递的顺序和实参传递的个数

int SubFunc(int a, int b)
{
	return a - b;
}

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};


int main()
{
	//1.改变实参参数顺序
	function<int(int, int)>f1 = SubFunc;
	function<int(int, int)>f2 = bind(SubFunc, placeholders::_1, placeholders::_2);
	cout << f1(1, 2) << endl;
	cout << f1(1, 2) << endl;
	function<int(int, int)>f3 = bind(SubFunc, placeholders::_2, placeholders::_1);
	cout << f1(1, 2) << endl;

	//2.改变实参参数个数
	function<int(Sub, int, int)>f4 = &Sub::sub;
	cout << f4(Sub(), 1, 2) << endl;
	function<int(Sub,int, int)>f5 = bind(&Sub::sub,  placeholders::_1, placeholders::_2, placeholders::_3);
	cout << f5(Sub(), 1, 2) << endl;
	function<int(int, int)>f6 = bind(&Sub::sub,Sub(), placeholders::_1, placeholders::_2);
	cout << f6(1, 2) << endl;
	function<int(int)>f7 = bind(&Sub::sub, Sub(),1,placeholders::_1);
	cout << f7(2) << endl;
	
	return 0;
}

运行结果如下。 

不难发现,我们通过bind绑定,实现了实参传递时的顺序和个数的不同。 

线程库 

之前,在学习Linux时,我们已经学习过了线程库的概念,我们在Linux中使用的是Pthread线程库。在C++11中我们也引入了线程库的概念,不过C++11中的线程库是被封装在了一个thread的类里。

通过查看C++文档不难发现,thread线程对象可以调用无参构造创建,但是创建之后不进行任何操作,同时thread线程对象,不允许被拷贝构造生成,但是允许移动构造生成,通过thread线程对象不允许调用赋值运算符重载进行赋值,但是可以调用移动赋值进行赋值。 

 情景:创建一个全局变量,使得两个线程对其进行++操作。

代码如下。

int x = 0;
void handle(int n)
{
	for (int i = 0; i < n; i++)
	{
		++x;
	}
}

int main()
{
	thread t1(handle,5000);
	thread t2(handle,5000);
	t1.join();
	t2.join();
	cout << x << endl;
	

	return 0;
}

运行结果如下。

两个线程分别对全局变量x,++5000次,最终打印出来的x的值是10000,貌似结果也没有什么问题,如果我们让每个线程都对x,++50000次呢?

按道理说此事的x应该是100000,但是打印出来的结果却是55330,很明显这出了问题,我们称之为线程安全问题,为什么会出现这种问题呢?其实在Linux系统编程中我们已经遇到了类似的问题,这是因为++操作分为三步,第一步,将寄存器中的x的值拿出来;第二步,CPU对x进行++操作;第三步,将++之后的x值放回寄存器,最后由操作系统将最终寄存器的值加载到内存中。 正是因为有了这三步,就增加了风险的概率,第一个线程加x值++之后还没有来的急放回寄存器,第二个线程就又对寄存器中的值进行了++,并且最终将++之后的x值返回到了寄存器中,并且更新到了内存中,此时第一个线程又将++之后的x的值放回寄存器,更新到了内存中,所以此时可能两次++操作,但是内存中x的值,只被++了一次。

怎么解决这样的线程安全问题呢?我们引入了互斥锁的概念,C++中也是有互斥锁的,文档如下。

所以我们就要对++操作进行加锁,但是问题又来了我们是加到for循环内部还是for循环外部呢?

我们建议将锁加在for循环的外面。

为什么要加在for循环的外面呢?

我们先简单分析一下,如果锁加在了for循环的外面,其实两个线程是串行运行的,如果锁加在了for循环的内部,其实两个线程是并行运行的,所以按照道理来说,应该是锁加在for循环内部效率更高,为什么还要加在for循环外呢?

这是因为虽然锁加在了内部是一个并行处理的过程,但是锁只有一把,当一把锁被一个线程占用时,另一个线程就只能被放入阻塞队列中去等待锁资源,与此同时操作系统要保存当前线程的上下文,当前线程获取到了锁资源时,就会从阻塞队列中剥离出来,然后操作系统会恢复其上下文,然后当前线程再去执行。正是因为如此,操作系统对上下文的保存和恢复也是需要耗费时间的,大量的加锁和解锁就意味着多次的上下文的保存和恢复,会去耗费额外大量的时间,所以我们推荐将锁加在for循环的外面。 

如果不使用锁,还有什么方法,可以避免上述隐患呢?

其实还有一种方法,就是原子操作。

先不使用原子操作,现在for循环外使用锁,我们查看代码的运行时间。

int main()
{
	//atomic<int> x = 0;
	int x = 0;
	mutex mt;
	int costime = 0;
	thread t1([&x, &mt,&costime](int n)
		{
			int begin1 = clock();
			mt.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			mt.unlock();
			int end1 = clock();
			costime += end1 - begin1;
		},50000000);

	thread t2([&x, &mt,&costime](int n)
		{
			int begin2 = clock();
			mt.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			mt.unlock();
			int end2 = clock();
			costime += end2 - begin2;
		}, 50000000);
	t1.join();
	t2.join();

	cout<< x << endl;
	cout << costime << endl;
	return 0;
}

运行结果如下。 

 

不难发现,代码的运行时间没293毫秒。

如果我们使用原子操作,代码如下。

int main()
{
	atomic<int> x = 0;
	mutex mt;
	int costime = 0;
	thread t1([&x, &mt,&costime](int n)
		{
			int begin1 = clock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			int end1 = clock();
			costime += end1 - begin1;
		},50000000);

	thread t2([&x, &mt,&costime](int n)
		{
			int begin2 = clock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			int end2 = clock();
			costime += end2 - begin2;
		}, 50000000);
	t1.join();
	t2.join();

	cout<< x << endl;
	cout << costime << endl;
	return 0;
}

运行结果如下。 

 虽然还是和加锁有差异,但是也是一种不错的避免加锁而实现线程安全的方法。 

以上便是线程库相关的知识。 

本期内容到此结束^_^

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 数据库编程-C++

目录 1 数据库基本知识 1.1 MYSQL常见命令 1.2 SQL注入 1.3 ORM框架 1 数据库基本知识 MySQL 为关系型数据库(Relational Database Management System), 这种所谓的"关系型"可以理解为"表格"的概念, 一个关系型数据库由一个或数个表格组成&#xff1a…

【算法篇】贪心算法

目录 贪心算法 贪心算法实际应用 一&#xff0c;零钱找回问题 二&#xff0c;活动选择问题 三&#xff0c;分数背包问题 将数组和减半的最小操作次数 最大数 贪心算法 贪心算法&#xff0c;是一种在每一步选择中都采取当前状态下的最优策略&#xff0c;期望得到全局最优…

5 计算机网络

5 计算机网络 5.1 OSI/RM七层模型 5.2 TCP/IP协议簇 5.2.1:常见协议基础 一、 TCP是可靠的&#xff0c;效率低的&#xff1b; 1.HTTP协议端口默认80&#xff0c;HTTPSSL之后成为HTTPS协议默认端口443。 2.对于0~1023一般是默认的公共端口不需要注册&#xff0c;1024以后的则需…

动态规划LeetCode-1035.不相交的线

在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j]且绘制的直线不与任何其他连线&#xff08;非水平线&#xff09;相…

禅道社区版项目管理软件部署(记录篇)

系统要求&#xff08;这里推荐使用docker容器化方式&#xff09;安装前的准备Docker快速安装最后通过查看地址验证是否部署成功开始界面化安装配置 禅道&#xff08;ZenTao&#xff09;是一款国产开源的项目管理软件&#xff0c;专注于敏捷开发流程&#xff0c;支持 Scrum 和 K…

数据结构-基础

1、概念&#xff1a; 程序 数据结构 算法 2、程序的好坏 可读性&#xff0c;稳定性&#xff0c;扩展性&#xff0c;时间复杂度&#xff0c;空间复杂度。 3、数据结构 是指存储、组织数据的方式&#xff0c;以便高效地进行访问和修改。通过选择适当的数据结构&#xff0c; 能…

从零开始:OpenCV 图像处理快速入门教程

文章大纲 第1章 OpenCV 概述 1.1 OpenCV的模块与功能  1.2 OpenCV的发展 1.3 OpenCV的应用 第2章 基本数据类型 2.1 cv::Vec类 2.2 cv&#xff1a;&#xff1a;Point类 2.3 cv&#xff1a;&#xff1a;Rng类 2.4 cv&#xff1a;&#xff1a;Size类 2.5 cv&#xff1a;&…

1-kafka服务端之延时操作前传--时间轮

文章目录 背景时间轮层级时间轮时间轮降级kafka中的时间轮kafka如何进行时间轮运行 背景 Kafka中存在大量的延时操作&#xff0c;比如延时生产、延时拉取和延时删除等。Kafka并没有使用JDK自带的Timer或DelayQueue来实现延时的功能&#xff0c;而是基于时间轮的概念自定义实现…

Java 注解使用教程

简介 Java 1.5 引入了注解&#xff0c;现在它在 Java EE 框架&#xff08;如 Hibernate、Jersey 和 Spring &#xff09;中被大量使用。Java 注释是该语言的一个强大特性&#xff0c;用于向 Java 代码中添加元数据。它们不直接影响程序逻辑&#xff0c;但可以由工具、库或框架…

第17章 读写锁分离设计模式(Java高并发编程详解:多线程与系统设计)

1.场景描述 对资源的访问一般包括两种类型的动作——读和写(更新、删除、增加等资源会发生变化的动作)&#xff0c;如果多个线程在某个时刻都在进行资源的读操作&#xff0c;虽然有资源的竞争&#xff0c;但是这种竞争不足以引起数据不一致的情况发生&#xff0c;那么这个时候…

强化学习 DAY1:什么是 RL、马尔科夫决策、贝尔曼方程

第一部分 RL基础&#xff1a;什么是RL与MRP、MDP 1.1 入门强化学习所需掌握的基本概念 1.1.1 什么是强化学习&#xff1a;依据策略执行动作-感知状态-得到奖励 强化学习里面的概念、公式&#xff0c;相比ML/DL特别多&#xff0c;初学者刚学RL时&#xff0c;很容易被接连不断…

【STM32系列】利用MATLAB配合ARM-DSP库设计FIR数字滤波器(保姆级教程)

ps.源码放在最后面 设计IIR数字滤波器可以看这里&#xff1a;利用MATLAB配合ARM-DSP库设计IIR数字滤波器&#xff08;保姆级教程&#xff09; 前言 本篇文章将介绍如何利用MATLAB与STM32的ARM-DSP库相结合&#xff0c;简明易懂地实现FIR低通滤波器的设计与应用。文章重点不在…

DeepSeek-R1 本地电脑部署 Windows系统 【轻松简易】

本文分享在自己的本地电脑部署 DeepSeek&#xff0c;而且轻松简易&#xff0c;快速上手。 这里借助Ollama工具&#xff0c;在Windows系统中进行大模型部署~ 1、安装Ollama 来到官网地址&#xff1a;Download Ollama on macOS 点击“Download for Windows”下载安装包&#x…

Llama最新开源大模型Llama3.1

Meta公司于2024年7月23日发布了最新的开源大模型Llama 3.1&#xff0c;这是其在大语言模型领域的重要进展。以下是关于Llama 3.1的详细介绍&#xff1a; 参数规模与训练数据 Llama 3.1拥有4050亿&#xff08;405B&#xff09;参数&#xff0c;是目前开源领域中参数规模最大的…

Linux之安装docker

一、检查版本和内核是否合格 Docker支持64位版本的CentOS 7和CentOS 8及更高版本&#xff0c;它要求Linux内核版本不低于3.10。 检查版本 cat /etc/redhat-release检查内核 uname -r二、Docker的安装 1、自动安装 Docker官方和国内daocloud都提供了一键安装的脚本&#x…

2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题3)-网络部分解析-附详细代码

目录 附录1:拓扑图 附录2:地址规划表 1.SW1 2.SW2 3.SW3 4.SW4 5.SW5 6.SW6 7.SW7 8.R1 9.R2 10.R3 11.AC1 12.AC2 13.AP2 14.AP3 15.EG1 16.EG2 附录1:拓扑图 附录2:地址规划表 设备

Vim跳转文件及文件行结束符EOL

跳转文件 gf 从当前窗口打开那个文件的内容&#xff0c;操作方式&#xff1a;让光标停在文件名上&#xff0c;输入gf。 Ctrlo 从打开的文件返回之前的窗口 Ctrlwf 可以在分割的窗口打开跳转的文件&#xff0c;不过在我的实验不是次次都成功。 统一行尾格式 文本文件里存放的…

《Angular之image loading 404》

前言&#xff1a; 千锤万凿出深山&#xff0c;烈火焚烧若等闲。 正文&#xff1a; 一。问题描述 页面加载图片&#xff0c;报错404 二。问题定位 页面需要加载图片&#xff0c;本地开发写成硬编码的形式请求图片资源&#xff1a; 然而部署到服务器上报错404 三。解决方案 正确…

Windows Docker笔记-Docker容器操作

在文章《Windows Docker笔记-Docker拉取镜像》中&#xff0c;已经拉取成功了ubuntu镜像&#xff0c;本章来讲解如何通过镜像来创建容器并运行容器。 这里再类比一下&#xff0c;加深理解&#xff0c;比如&#xff0c;我们现在想开一个玩具厂&#xff0c;我们的最终目的肯定是想…

upload-labs安装与配置

前言 作者进行upload-labs靶场练习时&#xff0c;在环境上出了很多问题&#xff0c;吃了很多苦头&#xff0c;甚至改了很多配置也没有成功。 upload-labs很多操作都是旧时代的产物了&#xff0c;配置普遍都比较老&#xff0c;比如PHP版本用5.2.17&#xff08;还有中间件等&am…