掌握c++异常处理:你需要知道的一切
一、异常处理基本语法
C++的提供的关于异常的三个关键字: try{ throw } catch{ }
。
#include <stdexcept>
#include <limits>
#include <iostream>
using namespace std;
void MyFunc(int c)
{
if (c > numeric_limits< char> ::max())
throw invalid_argument("throw MyFunc argument too large.");
//...
}
int main()
{
try
{
MyFunc(256); //cause an exception to throw
}
catch (invalid_argument& e)
{
cerr << "catch " << e.what() << endl;
return -1;
}
//...
return 0;
}
try
在块中,如果引发异常,则它将被其类型与异常匹配的第一个关联catch
块捕获。 换言之,执行从 throw
语句跳转到 catch
语句。 如果未找到可用的 catch
块, std::terminate
则将调用并退出程序。 在 c + + 中,可能会引发任何类型,但是,建议引发直接或间接从 std::exception
派生的类型。 在上面的示例中,异常类型 invalid_argument
在标头文件的标准库<stdexcept>
中定义。
语法比较简单:throw(抛出)一个数据,然后再用catch(捕获)接收。throw的数据类型可以是任意的,所以当然也可以是一个对象:
struct Test
{
Test(const char* s, int i, double d): s(s), i(i), d(d) {};
const char* s;
int i;
double d;
void print() const
{
printf("%s %d %.2f\n", s, i, d);
}
};
int main()
{
try
{
throw Test("LLF", 520, 13.14);
}
catch (const Test& e)
{
e.print();
}
}
二、基本指导原则
强大的错误处理对于任何编程语言都很有挑战性。 尽管异常提供了多个支持良好错误处理的功能,但它们无法完成所有工作。 若要实现异常机制的优点,请在设计代码时记住异常。
- 使用断言来检查绝不应发生的错误。 使用异常来检查可能出现的错误,例如,公共函数参数的输入验证中的错误。 有关详细信息,请参阅 异常与断言 部分。
- 当处理错误的代码与通过一个或多个干预函数调用检测到错误的代码分离时,使用异常。 当处理错误的代码与检测到错误的代码紧密耦合时,考虑是否使用错误代码而不是在性能关键循环中。
- 对于可能引发或传播异常的每个函数,请提供以下三种异常保证之一:强保障、基本保证或nothrow (noexcept) 保证。 有关详细信息,请参阅 如何设计异常安全性。
- 按值引发异常,按引用来捕获异常。 不要捕获无法处理的内容。
- 不要使用 c + + 11 中已弃用的异常规范。 有关详细信息,请参阅异常规范和 noexcept 部分。
- 应用时使用标准库异常类型。 从 exception 类层次结构派生自定义异常类型。
- 不允许对析构函数或内存释放函数进行转义。
三、Exception 类
对上面代码的分析,可以看到,发生异常时抛出一个对象而不是一个简单的数据类型,可以传递更多的错误信息,但是这样的话,需要针对不同的异常情况定义不同的类。有没有统一的解决方法?
C++给出来了一个标准的异常类Exception。看一下定义:
/**
* @brief Base class for all library exceptions.
*
* This is the base class for all exceptions thrown by the standard
* library, and by certain language expressions. You are free to derive
* your own %exception classes, or use a different hierarchy, or to
* throw non-class data (e.g., fundamental types).
*/
class exception
{
public:
exception() noexcept { }
virtual ~exception() noexcept;
exception(const exception&) = default;
exception& operator=(const exception&) = default;
exception(exception&&) = default;
exception& operator=(exception&&) = default;
/** Returns a C-style character string describing the general cause
* of the current error. */
virtual const char* what() const noexcept;
};
主要就是定义了一个what的虚函数,返回C_style的字符串,主要作用就是描述发生一场的原因。在使用的时候,往往需要自定义一个异常类:
#include<exception>
#include<iostream>
using namespace std;
class MyException:public exception
{
public:
const char* what()const throw(){ //throw () 表示不允许任何异常产生
return "ERROR! Don't divide a number by integer zero.\n";
}
};
void check(int y) throw(MyException)
{ //throw (MyException)表示只允许myException的异常发生
if(y==0) throw MyException();
}
int main()
{
int x=100,y=0;
try{
check(y);
cout<<x/y;
}catch(MyException& me){
cout<<me.what();
cout << "finish exception\n";
return -1;
}
cout << "finish ok\n";
return 0;
}
四、标准异常扩展
C++定义了一些标准的异常,用于各种场景,他们都是继承自std::exception的:
下表是对上面层次结构中出现的每个异常的说明:
异常 | 描述 |
---|---|
std::exception | 该异常是所有标准 C++ 异常的父类。 |
std::bad_alloc | 该异常可以通过 new 抛出。 |
std::bad_cast | 该异常可以通过 dynamic_cast 抛出。 |
std::bad_exception | 这在处理 C++ 程序中无法预期的异常时非常有用。 |
std::bad_typeid | 该异常可以通过 typeid 抛出。 |
std::logic_error | 理论上可以通过读取代码来检测到的异常。 |
std::domain_error | 当使用了一个无效的数学域时,会抛出该异常。 |
std::invalid_argument | 当使用了无效的参数时,会抛出该异常。 |
std::length_error | 当创建了太长的 std::string 时,会抛出该异常。 |
std::out_of_range | 该异常可以通过方法抛出,例如 std::vector 和std::bitset<>::operator。 |
std::runtime_error | 理论上不可以通过读取代码来检测到的异常。 |
std::overflow_error | 当发生数学上溢时,会抛出该异常。 |
std::range_error | 当尝试存储超出范围的值时,会抛出该异常。 |
std::underflow_error | 当发生数学下溢时,会抛出该异常。 |
五、std::exception_ptr
根据官方文档的介绍 std::exception_ptr是一个指向 exception object 的共享智能指针。
关键在于理解 “exception object” 是什么,是std::exception类的对象吗?这种理解是不准的,按我的理解,所谓“exception object” 应该是被throw抛出的对象,根据我们上面的学习,它既可以是int、double等简单的数据类型、也可以是自定义的类对象,当然也可以是std::exception类对象。
有四个操作std::exception_ptr的函数:
前两个用于生成一个std::exception_ptr,最后一个用于将exception_ptr指向的异常对象重新抛出(重新这个词语是相对于current_exception而言的)。
直接看官方的代码。
exception_ptr示例:
#include <iostream> // std::cout
#include <exception> // std::exception_ptr, std::current_exception,
std::rethrow_exception
#include <stdexcept> // std::logic_error
int main ()
{
std::exception_ptr p;
try {
throw std::logic_error("some logic_error exception"); // throws
} catch(const std::exception& e) {
p = std::current_exception();
std::cout << "exception caught, but continuing...\n";
}
std::cout << "(after exception)\n";
try {
std::rethrow_exception (p);
} catch (const std::exception& e) {
std::cout << "exception caught: " << e.what() << '\n';
}
return 0;
}
- 首先定义了一个 std::exception_ptr变量p。
- 然后在第一个try中,抛出了一个标准异常(见上)。
- 在第一个catch中,调用 current_exception() ,这样就让p指向了捕获的异常对象。
- 然后在第二个try中,调用 rethrow_exception ,将异常重新抛出。
- 然后在第二个catch中,依然正常的捕获到了这个异常对象。
make_exception_ptr示例:
#include <iostream> // std::cout
#include <exception> // std::make_exception_ptr, std::rethrow_exception
#include <stdexcept> // std::logic_error
int main ()
{
auto p = std::make_exception_ptr(std::logic_error("logic_error"));
try {
std::rethrow_exception (p); // 重新抛出异常
} catch (const std::exception& e) {
std::cout << "exception caught: " << e.what() << '\n'; // 捕获异常
}
return 0;
}
- 首先创建了一个异常make_exception_ptr。
- 然后再try中抛出该异常。
- 接着在catch捕获抛出的异常。
嵌套异常:nested_exception::nested_ptr。
#include <iostream> // std::cerr
#include <exception> // std::exception, std::throw_with_nested,
std::rethrow_if_nested
#include <stdexcept> // std::logic_error
// recursively print exception whats:
void print_what (const std::exception& e)
{
std::cout << __FUNCTION__ << ", L"<< __LINE__ << ", what:" << e.what() <<'\n';
try {
std::rethrow_if_nested(e);
} catch (const std::exception& nested) {
std::cerr << "nested: ";
print_what(nested);
}
}
// throws an exception nested in another:
void throw_nested()
{
try {
throw std::logic_error ("first");
} catch (const std::exception& e) {
std::throw_with_nested(std::logic_error("second"));
}
}
int main ()
{
try {
std::cout << __FUNCTION__ << ", L"<< __LINE__ << std::endl;
throw_nested();
} catch (std::exception& e) {
std::cout << __FUNCTION__ << ", L"<< __LINE__ << std::endl;
print_what(e);
}
return 0;
}
总结
通过本文的学习,对C++异常处理有了更深入的了解。学会如何使用基本的语法来处理异常,以及遵循异常处理的基本指导原则。同时,了解如何创建自定义异常类以及如何扩展标准异常类。学会如何使用std::exception_ptr来处理异常的指针,以及在多线程环境下的异常处理方法。
学习推荐: