C++异常

throw && Exception

在这里插入图片描述
内存不足,抛出bad_alloc异常
logic_error中的length_error:长度错误
out_of_range:越界异常
invalid_argument:参数非法

exception和logic_error和runtime_error是定义在头文件<exception>中的
bad_alloc是定义在<new>中的,当使用new或new[]时若内存不足,则抛出std::bad_alloc异常
bad_cast是定义在<type_info>中的,若daynamic_cast对引用进行转换类型的话,若失败,则抛出std::bad_cast异常;若对指针进行转换类型的话,若失败,返回空指针
std::system_error是与操作系统进行交互的过程中出现的异常,比如std::thread
<chrono>是与时间相关的头文件,其中定义了std::nonexistent_local_time和std::ambiguous_local_time两个异常

std::vector<int> v;
v.at(0) = 42;//检查下标是否合法,不合法抛出std::out_of_range异常
v[0] = 42;//不检查下标,但是可能会导致segmentation fault

栈展开/栈的开解

在这里插入图片描述
假设在int* p = new int[n];中出现了bad_alloc异常,则会出现栈回退,一些局部变量会被销毁
①在operator new[]中抛出bad_alloc异常
②控制流返回到int* p = new int[n];语句
③x出栈,被销毁
④n出栈,被销毁
⑤控制流返回到main函数中的func(size);语句
⑥size出栈,被销毁
栈展开:消除局部变量,退回到上个函数,再销毁局部变量,退回…
只针对被catch的异常才会出现栈展开

try catch

#include <iostream>
#include <exception>

void func(int n) {
	int x = 42;
	int* p = new int[n];
}

int main() {
	try
	{
		int size = 100;
		func(size);
	}
	catch (const std::bad_alloc& e)//异常传递要引用传递,尽量不拷贝
	{
		//处理异常
	}
}

栈展开时退到了try发现异常会被处理,接着查看下面的catch是否有异常对应,然后处理异常

继承于exception的异常都有一个what函数,用于输出语句

#include <iostream>
#include <exception>

void func() {
	throw std::runtime_error("I love u");
}

int main() {
	try
	{
		func();
	}
	catch (const std::runtime_error& e)
	{
		std::cout << e.what() << std::endl;//输出“I love u”
	}

	return 0;
}
void func(const std::vector<int> &v) {
	try
	{
		int i = 42;
		std::vector<int> copy = v;//可能会出现std::bad_alloc异常
		int x = copy.at(100);//可能会抛出std::out_of_range异常
		g(x);
	}
	catch (const std::bad_alloc& ba)
	{

	}
	catch (const std::out_of_range& oor) {

	}
	catch (...) {
		throw;//单写一个throw是把接受的异常再抛出去 
	}
	
	//后面的代码
}

若int x = copy.at(100);抛出out_of_range异常,程序进行倒退,销毁copy,i,然后到达try,找catch中是否有匹配的异常;catch (…)可匹配任何抛出的东西,不一定是异常
out_of_range在catch (const std::out_of_range& oor)处匹配并进行了处理,然后控制流可以执行后面的代码

若类中的构造函数的初始化出现异常,则在函数外使用try catch,try中包含了m_size(n), m_data(new T[n]{})等参数初始化操作,也包含了{}函数体部分

template <class T>
class Array {
public:
	Array(std::size_t n) try : m_size(n), m_data(new T[n]{}) {}
	catch (const std::bad_alloc& e) {
		std::cerr << "no enough memory" << std::endl;
		throw;
	}

private:
	int m_size;
	int arr[m_size];
};

自定义异常类

class WrongAnswer : public std::logic_error {
public:
	WrongAnswer(std::size_t line_number) : std::logic_error("Wrong answer on line" + std::to_string(line_number)) {};
	//自定义一个异常类,继承自logical_error,输出出现异常的行数
};

#define assert(X) {
if (!(X)) throw WrongAnswer(__LINE__);//__LINE__是一个宏,返回行号
};

int main() {
	assert((2 + 1) == 6);
	return 0;
}

异常安全

异常安全指的是满足一下三种中的任意一个即可:
1.不抛出保证:即函数不抛出异常
2.强异常安全保证:函数执行过程中,异常被抛出之后,能够让程序回退到调用异常函数之前的状态
3.弱异常安全保证:函数执行中,异常被抛出,很难去回退到调用异常函数之前的状态,但是可以把程序设置成有效的状态

异常说明符

在C++11中,可以在函数名后面抛出可能的异常:

void *operator(std::size_t size) throw(std::bad_alloc) {};

如果声明此函数不抛出异常:

void *operator(std::size_t size) noexcept {};

函数不抛出异常noexcept可使函数效率更高

以拷贝控制为例

1.

class Array {
	int *m_data;
	std::size_t m_size;

public:
	Array &operator=(const Array &other) {
		if (this != &other) {//检查是否是自我拷贝
			delete[] m_data;
			m_data = new int[other.m_size];//此处可能会抛出异常
			std::copy(other.m_data, other.m_data + other.m_size, m_data);
			m_size = other.m_size;
		}

		return *this;
	}
};

m_data = new int[other.m_size];会抛出std::bad_alloc异常,然后进行栈展开,对原来进行的操作进行撤销,包括局部变量销毁,但是delete掉的m_data找不回来了,但是m_size还没有改。因此此类不是异常安全的
改为:

class Array {
	int *m_data;
	std::size_t m_size;

public:
	Array &operator=(const Array &other) {
		if (this != &other) {//检查是否是自我拷贝
			auto new_data = new int[other.m_size];//此处可能会抛出异常
			std::copy(other.m_data, other.m_data + other.m_size, m_data);
			delete[] m_data;
			m_data = new_data;
			m_size = other.m_size;
		}

		return *this;
	}
};

具备强异常安全保证

2.

以下代码是强异常安全保证

class Array {
	int *m_data;
	std::size_t m_size;

public:
	void swap(Array &other) noexcept {
		using std::swap;
		swap(m_size, other.m_size);
		swap(m_data, other.m_data);
	}

	Array &operator=(const Array &other) {
		Array(other).swap(*this);//Array(other)中隐藏了new,会抛出异常
		return *this;
	}
};

3.

template <class T>
class Array {
	T *m_data;
	std::size_t m_size;

public:
	Array &operator=(const Array &other) : m_data(new T[other.m_size]), m_size(other.m_size) {//new会抛出异常
		std::copy(other.m_data, other.m_data + other.m_size, m_data);//std::copy也会抛出异常,因为现在这个T可能拷贝会出现异常,比如string就需要分配内存
		return *this;
	}
};

new会抛出异常的异常没关系,因为是一开始出现的,进行栈展开时没影响
但是std::copy的异常会出现问题,因为回退的时候m_size和m_data需要进行析构,但是m_data中有new了一块内存,会出现内存泄漏,所以需要try-catch一下,比如:

template <class T>
class Array {
	T *m_data;
	std::size_t m_size;

public:
	Array &operator=(const Array &other) : m_data(new T[other.m_size]), m_size(other.m_size) {//new会抛出异常
		try
		{
			std::copy(other.m_data, other.m_data + other.m_size, m_data);//std::copy也会抛出异常,因为现在这个T可能拷贝会出现异常,比如string就需要分配内存
		}
		catch (...)
		{
			delete[] m_data;
			throw;
		}
		return *this;
	}
};

从内存的角度看,这时就是强异常安全的
从数据的更改角度看,这是若异常安全的

4.

template <class T>
class Array {
	T *m_data;
	std::size_t m_size;

public:
	Array &operator=(const Array &other) {
		if (this != &other) {
			auto new_data = new T[other.m_size];//此处可能会抛出异常
			std::copy(other.m_data, other.m_data + other.m_size, m_data);//copy也会抛出异常
			delete[] m_data;
			m_data = new_data;
			m_size = other.m_size;
		}

		return *this;
	}
};

此代码中copy出现的异常不安全,因为会出现new_data刚new的内存出现泄露,所以需要try-catch一下,即

template <class T>
class Array {
	T *m_data;
	std::size_t m_size;

public:
	Array &operator=(const Array &other) {
		if (this != &other) {
			auto new_data = new T[other.m_size];//此处可能会抛出异常
			try {
				std::copy(other.m_data, other.m_data + other.m_size, m_data);//copy也会抛出异常
			}
			catch (...) {
				delete[] new_data;
				throw;
			}
			delete[] m_data;
			m_data = new_data;
			m_size = other.m_size;
		}

		return *this;
	}
};

5.

template <class T>
class Array {
	T *m_data;
	std::size_t m_size;

public:
	void swap(Array &other) noexcept {
		using std::swap;
		swap(m_size, other.m_size);
		swap(m_data, other.m_data);
	}

	Array &operator=(const Array &other) {
		Array(other).swap(*this);//Array(other)中隐藏了new,会抛出异常
		return *this;
	}
};

这是强异常安全的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值