【C++】异常

目录

简单使用

异常安全

异常规范

异常优缺点

实践中异常的使用机制


C语言处理错误的方式有哪些呢?assert断言;返回错误码errno,然后我们可以通过这个错误码查到错误信息(strerror(errno));我们可以自定义函数的返回值,如果有错误返回什么等等。同样抛异常就是C++处理错误的一种方式。

简单使用

首先我们认识抛异常要用到的三个关键字:throw try catch,我们来简单的写一个程序来用一下

double Division(int a, int b)
{
	if (b == 0)
		throw"The divisor is zero";
	else
		return ((double)a / (double)b);
}
void func()
{
	int a = 0, b = 0;
	cin >> a >> b;
	cout << Division(a, b) << endl;
}
int main()
{
	try
	{
		func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}

通过这个代码我们来说明一些语法及特性

throw可以抛出任意类型的对象,可以是内置类型,也可以是自定义类型。

try后边必须有catch,并且可以跟多个catch,同样catch上面必须要有try,但不能有多个try

抛出异常后必须要有catch接收,否则程序会异常终止

类型匹配才能捕捉到,不管外部有多少层,可以直接跳到catch,并且catch后继续向下执行

throw跳catch是跳到最近的类型匹配的catch,这个可以验证一下:我们可以在func函数中也进行捕获,如果类型匹配,就会在func中捕获,如果类型不匹配,就会到main函数中捕获

我们可以传引用捕获,就是说抛出异常对象后,会生成一个异常对象的拷贝(这里拷贝的产生可以是移动构造),因为异常对象会因为出了作用域而销毁,这个拷贝会在catch之后销毁。

我们上面说如果有到了main函数还没有捕获的异常,那么进程就会异常退出,所以为了避免这种情况,有一种catch方法可以捕获任意类型的异常

异常安全

就是说不要在一些地方抛出异常,否则会出错

比如说:不要在构造函数中抛异常,否则对象可能无法完全初始化,会造成访问野指针或内存泄漏的问题,比如这个类:

class A
{
public:
	A()
	{
		cout << "A()" << endl;
		a1 = new int(10);
		throw 111;
		a2 = new int (10);

	}
	~A()
	{
		delete a1;
		delete a2;
		cout << "~A()" << endl;
	}
private:
	int* a1;
	int* a2;
};

如果构造函数中途抛异常,那么因为对象没有初始化完,它是不会调用析构函数的,就会造成内存泄漏

同理,析构函数也不要抛异常,有可能会造成内存泄漏

还有这样一种情况,如果在普通函数中间new之后delete之前抛出了异常,那么我们需要new之后就捕获异常并且delete,然后再将异常重新抛出,因为不这么干就无法释放内存了,比如

void func()
{
	int* arr = new int[10];
	try
	{
		int a = 0, b = 0;
		cin >> a >> b;
		cout << Division(a, b) << endl;
	}
	catch (...)
	{
		cout << "delete []" << endl;
		delete[]arr;
		throw;
	}
	cout << "delete []" << endl;
	delete[]arr;
}

这样的话,无论是否出异常都不会造成内存泄漏

那如果有三个new呢?因为new本身也是会抛异常的,对于当前的我们只能这么写

void fxxx()
{
	int* p1 = new int(10);
	int* p2 = nullptr;
	int* p3 = nullptr;
	try 
	{
		p2 = new int(20);
		try
		{
			p3 = new int(30);
		}
		catch(...)
		{
			delete p1;
			delete p2;
			throw;
		}
	}
	catch (...)
	{
		delete p1;
		throw;
	}
}
int main()
{
	try
	{
		fxxx();
	}
	catch (...)
	{
		cout << "出现异常" << endl;
	}
	return 0;
}

非常麻烦,所以在我们后面学了智能指针后就可以这样写,就可以非常简单了

class smartptr
{
public:
	smartptr(int*ptr)
		:_ptr(ptr){}

	~smartptr()
	{
		delete[]_ptr;
	}
	
private:
	int* _ptr;
};
void fxxx()
{
	smartptr sp1(new int[10]);
	smartptr sp2(new int[20]);
	smartptr sp3(new int[30]);
}

异常规范

为了方便代码的可读性,我们可以在函数的后面加上这个函数会抛出了什么类型的异常,如果确定不抛出异常,那么可以加noexpect关键字,比如:

double Division(int a, int b)throw(string)//可以加多个类型
{
	if (b == 0)
		throw string("The divisor is zero");
	else
		return ((double)a / (double)b);
}
void func1()throw()//表示不会抛出异常
{
	cout << "func1" << endl;
}
void func2() noexcept//表示不会抛出异常
{
	cout << "func2" << endl;
}

当然这只是建议性的,有的内部抛了异常编译器也查不出来,并且只是会警告,所以现实意义并不大

异常优缺点

优点:

1.异常的对象定义好了,相比错误码的方式可以清晰准确的显示错误的各种信息,可以帮助更好的定位程序的bug

2.如果返回错误码的话,需要层层返回,但是异常可以直接跳多层,更方便

3.很多第三方库也使用异常,我们要使用库的话就要使用异常

4.有些函数只能返回特定的对象,无法返回错误码,比如T& at(size_t i);

缺点:

1.异常会导致程序的执行流乱跳,非常的混乱,这就导致我们在调试程序时非常困难

2.就像上面异常安全说的,容易出现内存泄漏的问题,我们就需要学习智能指针,就增加的学习成本

3.异常规范不是强制的,所以就会导致有人不遵守,有人随便抛异常,就会导致外层捕获的用户苦不堪言

实践中异常的使用机制

实践中一般定义一个基类,然后别的类去继承,抛出派生类的异常可以用基类捕获,这样对于我们的捕获错误就简单多了,下面是代码实例:

class Exception
{
public:
	Exception(const string&errmsg,int id)
		:_errmsg(errmsg)
		,_id(id){}
	virtual string what()const
	{
		return _errmsg;
	}
protected:
	string _errmsg;
	int _id;
};
class sqlException :public Exception
{
public:
	sqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id)
		, _sql(sql){}
	virtual string what()const
	{
		string str = "sqlException";
		str += _errmsg;
		str += "->";
		str += _sql;
		return str;
	}
private:
	const string _sql;
};
class CacheException :public Exception
{
public:
	CacheException(const string&errmsg,int id)
		:Exception(errmsg,id){}
	virtual string what()const
	{
		string str = "CacheException";
		str += _errmsg;
		return str;
	}
};
class HttpServerException :public Exception
{
public:
	HttpServerException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		,_type(type){}
	virtual string what()const
	{
		string str = "HttpServerException";
		str += _type;
		str += "->";
		str += _errmsg;
		return str;
	}
private:
	const string _type;
};
void SQLMgr()
{
	if (rand() % 7 == 0)
	{
		throw sqlException("权限不足", 100, "select*from name='张三'");
	}
	cout << "!!!!!!!!!!!!!!!!!!!运行成功" << endl;
}
void CacheMgr()
{
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}
	SQLMgr();
}
void HttpServer()
{
	if (rand() % 3 == 0)
	{
		throw HttpServerException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException("权限不足", 101, "post");
	}
	CacheMgr();
}
int main()
{
	srand(time(0));
	while (1)
	{
		Sleep(1000);
		try
		{
			HttpServer();
		}
		catch (const Exception& e)
		{
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "unkown Exception" << endl;
		}
	}
	return 0;
}
  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值