C语言处理错误的方式:
ps:assert在debug下起作用,在release下不起作用。
鉴于C语言中处理错误的方式不够理想和方便,C++推出了自己处理错误的方式——异常。当一个函数遇到无法处理的错误时,会抛出异常,让函数的调用者来处理。
先看看以下代码:
void func()
{
throw "error";
}
void test()
{
try
{
func();
}
catch (const char* str)
{
cout << str << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
}
ps:try语句可以接多条catch语句,用于捕获不同类型的异常。
ps:异常与对应类型的catch语句相匹配。
ps:catch (…)可用于捕获任意类型的异常,防止未知异常导致程序终止。
double Division(int len, int time)
{
if (time == 0)
{
throw "除零错误";
}
else
{
return (double)len / time;
}
}
void func()
{
throw 1;
}
void Func()
{
try
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
func();
}
catch (const char* str)
{
cout << str << endl;
}
}
int main()
{
try
{
Func();
}
catch (const char* str)
{
cout << str << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
}
ps:在异常被抛出时,会创建一个异常对象的拷贝或移动。这个拷贝或移动的结果,是用于在调用栈中向上传递的,直到找到匹配的catch块。这是为了确保在抛出点之后,原始对象的状态改变不会影响已经抛出的异常对象。
在C++中抛出异常时,会创建一个异常对象的拷贝或移动,以确保异常对象在抛出点和捕获点之间的生命周期和安全性。在C++11及之后的版本中,如果异常类型支持移动语义并且移动操作是高效的,那么编译器将倾向于使用移动构造函数来抛出异常。
有时你可能希望在catch块中捕获异常后,不立即处理它,而是选择重新抛出(rethrow)它,以便让更上层的代码有机会处理它。
请看看以下代码:
double Division(int len, int time)
{
if (time == 0)
{
throw "除零错误";
}
else
{
return (double)len / time;
}
}
void Func()
{
int* p = new int[10];
try
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
delete[] p;
throw;//捕到什么抛什么
}
delete[] p;
}
int main()
{
try
{
Func();
}
catch (const char* str)
{
cout << str << endl;
}
}
在上述场景中,因为可能抛异常导致动态开辟的空间不能释放,所以就需要在catch语句中先释放空间,再将异常重新抛出。
ps:异常重新抛出(throw;)时不会创建一个新的异常对象的拷贝或移动,而是继续传播当前捕获的异常对象。
这个过程是高效的,因为它避免了不必要的对象拷贝或移动。然而,这也意味着在 catch 块中修改捕获的异常对象的状态可能会影响到上层代码对这个异常对象的处理,因为它们是同一个对象。因此,在重新抛出异常之前,通常不建议修改捕获的异常对象的状态。
C++中异常会频繁导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题(智能指针章节)。
// 这里表示这个函数会抛出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;
thread (thread&& x) noexcept;
最重要的是,这些只是一个期望规范,编译器不会强制检查和报错,哪怕写的并不对(甚至可以不写)。
实际上,异常的抛出和捕获,有一种特例不需要类型相对应。那就是抛出派生类对象,用基类来捕获(这也是实际中非常实用的做法)。
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()
{
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(100);
try
{
HttpServer();
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
// 多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
}
用基类引用统一捕获不同的派生类对象,实现多态,规范了异常体系。防止有人乱抛异常,导致项目程序终止。
列举几个常见的标准异常:
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;
}
ps:operator[]不会做边界检查,如果你尝试访问一个越界的索引,operator[] 会返回对应位置的引用,但这是一个未定义的行为,可能会导致程序崩溃或其他不可预测的结果。
ps:C++标准异常库设计的不够好用,实际中很多公司都是自定义一套异常体系。
C++异常的优势:
C++异常的缺陷: