当前位置: 首页 > news >正文

bingc++(完美转发、线程库、特殊类、异常)

上一篇

目录标题

  • 完美转发(隐含 完美折叠)
  • 线程库
    • 线程函数参数
    • 原子性操作库
      • 自定义[原子类型](https://www.cnblogs.com/ittinybird/p/4830834.html)
    • lock_guard与unique_lock
      • lock_guard
      • unique_lock
  • 特殊类
    • 只能在堆上创建对象
    • 只能在栈上创建对象
  • 异常
    • 异常的抛出和匹配原则
    • setjmp和longjmp(c语言)
    • c++异常
      • 异常的重新抛出
    • 异常安全
    • 异常规范
    • 自定义异常体系

完美转发(隐含 完美折叠)

所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值

void Fun(int &x)
{ 
	cout << "lvalue ref" << endl; 
}

void Fun(int &&x)
{ 
	cout << "rvalue ref" << endl; 
}

void Fun(const int &x)
{ 
	cout << "const lvalue ref" << endl; 
}

void Fun(const int &&x)
{ 
	cout << "const rvalue ref" << endl; 
}

template<typename T>
void PerfectForward(T &&t)
{ 
	//Fun(t);
	Fun(std::forward<T>(t)); 
}

int main()
{
	PerfectForward(10); // rvalue ref
	int a;
	PerfectForward(a); // lvalue ref
	PerfectForward(std::move(a)); // rvalue ref
	const int b = 8;
	PerfectForward(b); // const lvalue ref
	PerfectForward(std::move(b)); // const rvalue ref
	return 0;
}

线程库

  1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
  2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
    get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中包含了一个结构体
#include <thread>

int main()
{
	thread t;
	cout << t.get_id() << endl;
	return 0;
}
typedef struct
{ /* thread identifier for Win32 */
 void *_Hnd; /* Win32 HANDLE */
 unsigned int _Id;
} _Thrd_imp_t;
  1. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下可按照以下三种方式提供:
  • 函数指针
#include <thread>
void ThreadFunc(int n)
{
	for (size_t i = 0; i < n; ++i)
	{
		cout << "thread!!!" << endl;
	}
	cout << "thread End" << endl;
}
int main()
{
	thread t1(ThreadFunc, 10);
	t1.join();  // 调用之后,主线程就会被阻塞,当t1对应的线程执行完成之后,main线程才继续往下执行
	cout << "main thread " << endl;
	return 0;
}
  • 函数对象
class ThreadFunc
{
public:
	void operator()(int x, int y)
	{
		cout << x + y << endl;
		cout << "thread End!!!" << endl;
	}
};

int main()
{
	thread t1(ThreadFunc(), 10, 20);
	t1.join();  // 调用之后,主线程就会被阻塞,当t1对应的线程执行完成之后,main线程才继续往下执行


	cout << "main thread " << endl;
	return 0;
}
  • lambda表达式
int main()
{
	thread t1([](){
		for (int i = 0; i < 10; ++i)
		{
			cout << "thread!!!" << endl;
		}
	});

	//t1.join();  // 调用之后,主线程就会被阻塞,当t1对应的线程执行完成之后,main线程才继续往下执行
	t1.detach();

	cout << "main thread " << endl;
	return 0;
}
  1. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
  2. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效
  • 采用无参构造函数构造的线程对象
  • 线程对象的状态已经转移给其他线程对象
  • 线程已经调用jion或者detach结束

线程函数参数

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改
后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参
测试

void ThreadFunc(int& ra)
{
	ra = 100;
	cout << &ra << endl;
}
int main()
{
	int a = 10;
	cout << &a << endl;

	// 注意:线程函数参数的ra引用的不是创建线程时传递的实参a
	// 创建线程时传递的a 将来被拷贝到线程私有的栈空间中了
	// ra实际应用的是线程栈空间中的a的拷贝
	thread t(ThreadFunc, a);
	t.join();
	cout << a << endl;
	return 0;
}

在这里插入图片描述
如果想改变对象,参数传指针即可或者用下面的方法

void ThreadFunc(int& ra)
{
	ra = 100;
	cout << &ra << endl;
}

int main()
{
	int a = 10;
	cout << &a << endl;
	thread t(ThreadFunc, std::ref(a));
	t.join();
	cout << a << endl;
	return 0;
}

在这里插入图片描述

原子性操作库

在这里插入图片描述

#include <atomic>
#include <thread>
// 采用原子类型
atomic_uint sum = { 0 };
void ThreadFunc(int n)
{
	for (size_t i = 0; i < n; ++i)
	{
		sum++;
	}
}
int main()
{
	thread t1(ThreadFunc, 10000000);
	thread t2(ThreadFunc, 10000000);

	t1.join();
	t2.join();

	cout << sum << endl;
	return 0;
}

自定义原子类型

atmoic<T> t; // 声明一个类型为T的原子类型变量t

注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。

#include <atomic>
int main()
{
 atomic<int> a1(0);
 //atomic<int> a2(a1); // 编译失败
 atomic<int> a2(0);
 //a2 = a1; // 编译失败
 return 0; }

lock_guard与unique_lock

lock_guard

c++11中定义

template<class _Mutex>
class lock_guard
{
public:
 // 在构造lock_gard时,_Mtx还没有被上锁
 explicit lock_guard(_Mutex& _Mtx)
 : _MyMutex(_Mtx)
 {
 _MyMutex.lock();
 }
 // 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁
 lock_guard(_Mutex& _Mtx, adopt_lock_t)
 : _MyMutex(_Mtx)
 {}
 ~lock_guard() _NOEXCEPT
 {
 _MyMutex.unlock();
 }
 lock_guard(const lock_guard&) = delete;
 lock_guard& operator=(const lock_guard&) = delete;
private:
 _Mutex& _MyMutex;
}

通过上述代码可以看到,lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock
使用

#include <mutex>
int number = 0;
mutex g_lock;

int ThreadProc1()
{
	for (int i = 0; i < 100; i++)
	{
		lock_guard<mutex> Lock(g_lock);
		++number;
		cout << "thread 1 :" << number << endl;
	}
	return 0;
}
int ThreadProc2()
{
	for (int i = 0; i < 100; i++)
	{
		lock_guard<mutex> Lock(g_lock);
		--number;
		cout << "thread 2 :" << number << endl;
		break;
	}
	return 0;
}

int main()
{
	thread t1(ThreadProc1);
	thread t2(ThreadProc2);
	t1.join();
	t2.join();
	cout << "number:" << number << endl;
	system("pause");
	return 0;}

unique_lock

与lock_gard类似,unique_lock类模板也是采用RAII的方式对锁进行了封装,并且也是以独占所有权的方式,管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。在构造(或移动(move)赋值)时,unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的 unique_lock 对象负责传入的 Mutex对象的上锁和解锁操作。使用以上类型互斥量实例化unique_lock的对象时,自动调用构造函数上锁,unique_lock对象销毁时自动调用析构函数解锁,可以很方便的防止死锁问题。 与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:
上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock
修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有权)、释放(release:返回它所管理的互斥量对象的指针,并释放所有权)
获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相同)、mutex(返回当前unique_lock所管理的互斥量的指针)。

特殊类

只能在堆上创建对象

class HeapOnly
{
public:
	static HeapOnly* GetHeapOnly(int ho)
	{
		return new HeapOnly(ho);
	}

	~HeapOnly()
	{

	}

	HeapOnly(const HeapOnly&) = delete;
	HeapOnly(HeapOnly&&) = delete;
private:
	HeapOnly(int ho)
		: _ho(ho)
	{}

private:
	int _ho;
};

只能在栈上创建对象

class StackOnly 
{ 
public: 
 static StackOnly CreateObject() 
 { 
 return StackOnly(); 
 }
private:
 StackOnly() {}
};

或者

class StackOnly 
{ 
public: 
 StackOnly() {}
private: 
 void* operator new(size_t size);
 void operator delete(void* p);
};

异常

异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
  4. **catch(…)**可以捕获任意类型的异常,问题是不知道异常错误是什么。
  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个。
#include <windows.h>
int main()
{
	FILE* pf = fopen("2222.txt", "r");
	if (nullptr == pf)
	{
		cout << "打开文件失败" << endl;
		//1.
		 cout << errno << endl;
		 //2.
		size_t errNo = GetLastError();
		cout << errNo << endl;
		return 0;
	}

	fclose(pf);
	return 0;
}

setjmp和longjmp(c语言)

// setjmp 和 longjmp的使用

jmp_buf buff;

void func1()
{
	FILE* pf = fopen("2222.txt", "r");
	if (nullptr == pf)
	{
		// 此处并不对该错误进行处理
		// 而是让程序执行流程跳转到专门处理错误的位置来处理该错误

		// buff表示将来要跳转到的位置,在跳转之前必须将buff中要跳转到的位置设置好
		// 1: 跳转之后直接赋值给setjmp左侧的变量,用来区分是那个函数中的longjmp发生了跳转
		longjmp(buff, 1);
	}

	// ...
	// 对文件进行操作

	fclose(pf);

}

void func2()
{
	int* p = (int*)malloc(sizeof(int)* 10);
	if (nullptr == p)
	{
		// 此处并不对该错误进行处理
		// 而是让程序执行流程跳转到专门处理错误的位置来处理该错误
		longjmp(buff, 2);
	}

	// ...

	free(p);
}


int main()
{
	// setjmp(buff):设置跳转点 并且 setjmp第一次调用时返回的是0
	// 将来longjmp(buff, num); 要跳转到buff标记的位置
	int istate = setjmp(buff);
	if (0 == istate)
	{
		func2();
		func1();
	}
	else
	{
		// 程序遇到了错误
		switch (istate)
		{
		case 1:
			cout << "func1: 打开文件失败" << endl;
			break;
		case 2:
			cout << "func2: malloc失败" << endl;
			break;
		default:
			cout << "未知错误" << endl;
		}
	}
	
	// 从该位置程序继续往下执行
	cout << "..." << endl;
	return 0;
}

c++异常

void func1()
{
	FILE* pf = fopen("2222.txt", "r");
	if (nullptr == pf)
	{
		throw 1;  // throw用来抛出异常的
	}

	// ...
	// 对文件进行操作

	fclose(pf);

}

int main()
{
	// 注意:对于可能会发生异常的函数调用时候,一定需要使用try-catch结构来尝试进行捕获并处理
	// 否则:程序将会崩溃

	// 注意:对于有可能会发生异常的代码一定要放在try中尝试进行捕获
	// catch: 会根据抛出异常的类型进行捕获,捕获到之后,在catch中可以对异常进行处理
	try
	{
		func1();
	}
	catch (int err)
	{
		cout << err << endl;
	}
	
	// 程序从此位置会继续往下执行...
	cout << "继续往下执行..." << endl;
	return 0;
}

异常的重新抛出

// 异常的重新抛出
void func1()
{
	FILE* pf = fopen("2222.txt", "r");
	if (nullptr == pf)
	{
		throw 1;
	}

	// ...
	// 对文件进行操作

	fclose(pf);

}
/*
func1想要将自己的异常交给main方法处理,即:func2不能捕获
但是func2不捕获又不行,因为如果不捕获,func2内部的空间就是泄漏
所以func2必须对func1抛出的异常捕获,注意:func2捕获的目的不是为了
处理该异常,而是为了释放自己内部的资源,因此func2最后还必须对func1中的
异常继续往出抛
*/
void func2()
{
	int* p = new int[10];
	try
	{
		func1();
	}
	catch (...)
	{
		delete[] p;
		throw;
	}

	delete[] p;
}

int main()
{
	try
	{
		func2();
	}
	catch (int err)
	{
		cout << err << endl;
	}

	_CrtDumpMemoryLeaks();
	return 0;
}

异常安全

  • 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
  • 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
  • C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题,

异常规范

  1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型。
  2. 函数的后面接throw(),表示函数不抛异常。
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。
// 假设:func一定不会抛出异常
// 如果一个方法确定一定不会抛出异常,只需再该函数参数列表之后跟上throw()
// 万一再实现时抛出了异常,则编译报错
void func()throw()
{
	// throw 1;
}


// 假和:一个方法中可能会抛出不同种类的异常,也可以对要抛出异常的类型来进行约束
// void func(int )throw(int, double, char): func只能抛出int、double、char类型的异常

void func(int)throw(int, double, char)
{
	// 应该抛出
	// throw 1;

	// 非法抛出
	throw "error";
}

自定义异常体系

// 一般情况,不会直接抛出int、double种类没有意义的异常,该种方式一般是学习时语法验证
// 在项目中,各个公司一般都会维护自己的异常体系结构,好处:捕获异常方便,而且对象可以携带更多的错误信息

#include <string>
class Excecption
{
public:
	Excecption(const string& errInfo = "", size_t errId = 0)throw()
		: _errInfo(errInfo)
		, _errId(errId)
	{}


	// 纯虚函数
	virtual void what()throw() = 0;
protected:
	string _errInfo;
	size_t _errId;
};


// 网络
class NetException : public Excecption
{
public:
	NetException(const string& errInfo = "", size_t errId = 0)throw()
		: Excecption(errInfo, errId)
	{}

	// 纯虚函数
	virtual void what()throw()
	{
		cout << _errInfo << ":" << _errId;
	}
};


// DBException
class DBException : public Excecption
{
public:
	DBException(const string& errInfo = "", size_t errId = 0)throw()
		: Excecption(errInfo, errId)
	{}

	// 纯虚函数
	virtual void what()throw()
	{
		cout << _errInfo << ":" << _errId;
	}
};



void func1()
{
	// 操作数据库
	DBException e("数据库连接失败", 301);
	throw e;
}

void func2()
{
	// 网络相关
	NetException e("网络连接失败", 501);
	throw e;
}


#include <exception>
#include <vector>
int main()
{
	try
	{
		vector<int> v;
		cout << v.at(100) << endl;
		func1();
		func2();
	}
	catch (Excecption& e)
	{
		e.what();
	}
	catch (exception& e)
	{
		e.what();
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
	return 0;
}

相关文章:

  • 2022年全国大学生数学建模美赛E题NPP数据获取
  • 爬虫的基本原理
  • 网络安全原来有这么多大厂,码住!
  • 2022 年最新【Java 经典面试 800 题】面试必备,查漏补缺;多线程 +spring+JVM 调优 + 分布式 +redis+ 算法
  • 嵌入式Linux:V3s移植NES游戏,声音,游戏手柄
  • 查看笔记本电池健康度的两种方法和电池报告解析
  • 神经网络自适应pid控制器,基于神经网络的pid控制
  • Leetcode 1441. 用栈操作构建数组
  • 数据结构与算法分析之排序算法
  • 利用STM32CubeMX快速创建点灯带调试输出工程案例
  • new 和 delete 为什么要匹配使用
  • 00后表示真干不过,部门新来的00后测试员已把我卷崩溃,想离职了...
  • 3年测试在职,月薪还不足20K,惨遭裁员,用亲身经历给大家提个醒..
  • 【从小白到大白03】类和对象-下
  • [DAX] MAX函数 | MAXX函数
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • angular学习第一篇-----环境搭建
  • Codepen 每日精选(2018-3-25)
  • Create React App 使用
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • js 实现textarea输入字数提示
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • magento 货币换算
  • Python 基础起步 (十) 什么叫函数?
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 基于 Babel 的 npm 包最小化设置
  • 如何解决微信端直接跳WAP端
  • 为视图添加丝滑的水波纹
  • 小试R空间处理新库sf
  • 自制字幕遮挡器
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 回归生活:清理微信公众号
  • #微信小程序:微信小程序常见的配置传旨
  • $.ajax()方法详解
  • $.ajax中的eval及dataType
  • (C语言)fread与fwrite详解
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (简单) HDU 2612 Find a way,BFS。
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (算法)N皇后问题
  • (一)基于IDEA的JAVA基础10
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • .bat批处理(六):替换字符串中匹配的子串
  • .Net IOC框架入门之一 Unity
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • [2016.7 day.5] T2
  • [22]. 括号生成
  • [AIGC] Kong:一个强大的 API 网关和服务平台
  • [AutoSar]工程中的cpuload陷阱(三)测试
  • [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-controller-manager失败