目录
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。(可以抛任意类型的对象)。这个对象的类型决定着激活那段catch代码。catch: 在你想要处理问题的地方,通过异常处理程序捕获异常 . catch 关键字用于捕获异常,可以有多个 catch 进行捕获。try: try 块中的代码为你认为可能发生异常,当它接收到块中的异常时将被激活的特定异常 , 它后面通常跟着一个或多个 catch 块。
try
{
?// 保护的标识代码
}catch( ExceptionName e1 )
{
?// catch 块
}catch( ExceptionName e2 )
{
?// catch 块
}catch( ExceptionName eN )
{
?// catch 块
}
举例:
#include<iostream>
using namespace std;
double Division(int len, int time)
{
if (time == 0)
{
throw"除0错误";
cout << "Division()" << endl;
}
else
{
return (double)len / time;
}
}
int main()
{
try
{
cin>>len<<time;
cout<<Division(len,time);
}
catch (const char* str)
{
cout << str << endl;
}
return 0;
}
这里Division函数是计算商的,用当除数为0时,直接用throw抛出异常,然后用try/cathc捕获。这里要想被捕获到,首先抛出异常的代码段必须要在try块中,这段代码也被称作保护代码。同时一旦throw,会直接跳到catch位置,后面的代码不会被执行。
如果你在throw时抛出一个字符串,但是在catch捕获异常时的参数却写的是一个整数那么这个抛出异常就不会去匹配这个catch。
try和catch不仅仅可以在一个作用域使用,还可以在最外层try,然后嵌套多层函数,在最里面的函数throw!
void a()
{
throw "出错误";
cout << "a" << endl;
}
void b()
{
a();
cout << "b" << endl;
}
void c()
{
b();
cout << "c" << endl;
}
int main()
{
try
{
c();
cout<<"c"<<endl;
}
catch (const char* str)
{
cout << str << endl;
}
return 0;
}
注:我们知道程序是按照顺序结构执行,在执行到C函数时,已经创建了C函数,b函数,a函数,main函数的栈帧。这里我们抛出异常是在C函数里,直接跳到了main函数里捕获。因此这里在main函数捕获异常之前,c,b,a函数的栈已经被销毁了。同时,因为还未执行c,b,a函数的打印其函数栈帧便被销毁,因此也不会执行。同时这里也不会执行try块里的cout。但是在catch之后的代码可以执行。
3.throw和catch遵循就近原则
在公司写大工程时,会和很多同事合作写代码,大家都会抛出异常,但是你不能确定是不是所有人抛出的类型你都有相应的catch可以接受,若抛出一个异常没有被捕获就会直接报错终端程序,所以C++为了解决接受一些你不知到类型的异常,采用了这种形式来兜底。
#include<iostream>
using namespace std;
double Division(int len, int time)
{
if (time == 0)
{
throw"除0错误";
cout << "Division()" << endl;
}
else
{
return (double)len / time;
}
}
void func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
cout << " 6666" << endl;
}
int main()
{
try
{
func();
}
//catch (const char* str)
//{
// cout << str << endl;
//}
catch (...)
{
cout << "未知类型的错误" << endl;
}
//catch (const char* str)
//{
// cout << str << endl;
//}
return 0;
}
注:这里当有catch(具体的接受类型对象)与catch(...)一起出现时,catch(具体的接受类型对象)必须要在catch(...)前面否则或报错,同时会优先于catch前者匹配。
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func()
{
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
// 重新抛出去。
int* array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
cout << "delete []" << array << endl;
delete[] array;
throw;//向外层重新抛异常
}
// ...
cout << "delete []" << array << endl;
delete[] array;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
return 0;
}
Func()函数申请了一段堆区资源,如果Division()函数抛出异常,不在Func()里捕获的话,执行流将会直接到达main函数的catch中,导致Func()函数申请的堆区资源没有释放,造成内存泄漏。所以要在Func()函数中对Division()的异常进行捕获,这里我们的目的并不是处理异常,目的是释放堆区空间,达成目的后要向外层重新抛出异常。
? ? ? ? 这里也说明了异常的出现,会导致程序执行流变得混乱,类似C语言的goto语句乱跳,所以在我们使用异常时要保证异常安全。
构造函数完成对象的构造和初始化 , 最好不要 在构造函数中抛出异常,否则 可能导致对象不完整或没有完全初始化
析构函数主要完成资源的清理 , 最好不要 在析构函数内抛出异常,否则 可能导致资源泄漏 ( 内存泄漏、句柄未关闭等 )
C++ 中异常经常会导致资源泄漏的问题,比如在 new 和 delete 中抛出了异常,导致内存泄漏,在 lock 和 unlock 之间抛出了异常导致死锁, C++ 经常使用 RAII 来解决以上问题,关于 RAII我们智能指针进行详谈。
#include<iostream>
#include<Windows.h>
using namespace std;
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()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
//throw "xxxxxx";
}
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("权限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
SQLMgr();
}
void HttpServer()
{
// ...
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("请求资源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("权限不足", 101, "post");
}
CacheMgr();
}
int main()
{
while (1)
{
Sleep(500);
try {
HttpServer();
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
/* int n = 10;
while(e._erid == 3 && n--)
{
// 重试
Seed
}*/
// 多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
抛出的是子类对象,捕获的是父类对象。如果异常被catch(...)捕获,说明有人没有按照规定抛出异常子类对象。
1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的 后面接throw( 类型 ) ,列出这个函数可能抛掷的所有异常类型。
2. 函数的后面接 throw() ,表示函数不抛异常。
3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;//如果这个类明明会抛异常,你还加上noexcept,编译器会崩溃的
thread (thread&& x) noexcept;
int main()
{
try {
vector<int> v(10, 5);
// 这里如果系统内存不够也会抛异常
v.reserve(1000000000);
// 这里越界会抛异常
v.at(10) = 100;
}
catch (const exception& e) // 这里捕获父类对象就可以
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
return 0;
}