1.C语言传统的处理错误的方式
1. 终止程序,如assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。
2. 返回错误码,缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误。
实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。
2. C++异常概念
在C++中,异常处理是一种用于处理程序运行时错误的机制,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。它允许程序在遇到错误时将控制权转移到专门的异常处理代码,从而避免程序崩溃。
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常,可以有多个catch进行捕获。
try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。 如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。
3. 异常的使用
3.1 异常的抛出和捕获
3.1.1异常的抛出和匹配原则
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码,catch(...)可以捕获任意类型的异常。
template<class T>
void Func(T arg)
{if (typeid(arg) == typeid(const char*))throw "const char*";else if (typeid(arg) == typeid(int))throw 1;elsethrow 1.0;
}
int main()
{try{Func("aaaa");//输出const char*//Func(1);//输出int//Func(1.0);//输出other}catch (int errmsg){cout << "int" << endl;}catch (const char* errmsg){cout << "const char*" << endl;}catch (...){cout << "other" << endl;}return 0;
}
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
void func3()
{throw "error!";
}
void func2()
{func3();
}
void func1()
{try{func2();}catch (const char* errmsg){cout <<"func1: " << errmsg << endl;}
}
int main()
{try{func1();}catch (const char* errmsg){cout <<"main: "<< errmsg << endl;}catch (...){cout << "unknow error" << endl;}return 0;
}
以上代码的调用链为main -> func1 -> func2 -> func3,其中func3抛出了异常,于是异常开始往回查找,先看看当前的func3抛出的异常在不在try中,再回到func2发现其也没有try,再回到func1,此时发现func1中在try内部调用了func2,因此执行func1内的catch。虽然最高层main也有try-catch,但是异常只匹配离自己最近的那个。
3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象, 使用基类捕获,这个在实际中非常实用。
// 定义派生类异常
class Derived : public Base
{
public:const char* what(){return "Derived Exception";}
};
int main()
{try{// 抛出派生类异常throw Derived();}catch (Base& e){// 捕获基类异常,但实际接收到的是派生类异常cout << e.what() << endl;}catch (...){cout << "unkown exception" << endl;}return 0;
}
3.2 示例
double Division(double a, double b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn (a / b);
}
void Func()
{double len, time;cin >> len >> time;cout << Division(len, time) << endl;
}
int main()
{try {Func();}catch (const char* errmsg) {cout << errmsg << endl;}catch(...) {cout<<"unkown exception"<<endl; }return 0;
}
上述代码中main调用Func,Func调用Division,因为Division可能会发生除0错误,所以Func函数是有可能间接发生异常的,此时就需要把调用Func的语句放到try块中,说明我们要检测这个Func函数会不会发生异常。
一旦Division抛出异常,那么就是Func发生了异常,此时try就可以检测到,由于抛出的异常是const char*,故而要检测该类型的异常,所以catch的参数就是const char*了,catch (const char* errmsg)。一旦catch匹配到了相同类型的异常,就会执行{ }中的代码。
3.3 异常的重新抛出
double Division(double a, double b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return a / b;
}
void Func()
{// 这里可以看到如果发生除0错误直接抛出异常,未在{}里释放array就会发生内存泄漏。// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去。int* array = new int[10];try {double len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw;//重新抛出异常时,被抛出的异常是可以省略的,直接throw即可}// 没有发生异常时cout << "delete []" << array << endl;delete[] array;
}
int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}
3.4 异常安全
class A
{
public:A(int n){if (n == 0)throw 0;_ptr = new int[n];}~A(){delete[] _ptr;}
private:int* _ptr;
};
以上代码中,A
类的构造函数在n = 0
的时候,直接抛出异常。此时会直接结束构造函数,去找catch,而使得_ptr并
没有初始化,为野指针。之后一旦出了A
的作用域,A
就会被销毁,调用析构函数,而析构函数要delete[]
,这就导致了严重的问题,程序直接崩溃。
template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr): _ptr(ptr){cout << "SmartPtr(T* ptr)" << endl;}~SmartPtr(){cout << " ~SmartPtr()" << endl;delete[] _ptr;}
private:T* _ptr;
};
void func()
{SmartPtr<int> p = new int[10];
}
int main()
{func();return 0;
}
3.5 异常规范
// 这里表示这个函数会抛出int/double/char/const char*中的某种类型的异常
void fun() throw(int,double,char,const char* );
// 这里表示这个函数只会抛出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;
4.自定义异常体系
// 服务器开发中通常使用的异常继承体系
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;}
};
void SQLMgr()
{if (rand() % 7 == 0){throw SqlException("权限不足", 100, "select * from name = '张三'");}//throw "xxxxxx";
}
void CacheMgr()
{if (rand() % 5 == 0){throw CacheException("权限不足", 100);}else if (rand() % 6 == 0){throw CacheException("数据不存在", 101);}
}
int main()
{srand((unsigned int)time(nullptr));while (1){Sleep(1000);try {SQLMgr();CacheMgr();}catch (const Exception& e) // 这里捕获父类对象就可以{// 多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}
5.C++标准库的异常体系
其中exception
是最大的基类,剩余的所有类都是派生类,它们各自代表了不同的异常,比如vector
越界时,就会抛出out_of_range
异常。
#include<iostream>
#include<vector>
using namespace std;
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;
}