概述
C++标准库是怎么对待异常的?
C++标准程序库有两种方式:
- 标准程序库中有一部分,例如
std classes
,支持具体的错误处理,他们检测所有可能发生的错误,并于错误发生时抛出异常 - 像
STL
,valarrays
等,效率重于安全,因此几乎不检验逻辑错误,并且只检测执行期(runtime
)错误
ps:逻辑错误和运行期程序的区别:
- 逻辑错误:可以被避免,基本上是你代码逻辑出错,其错误原因发生在程序内作用域内
- 运行期异常:由一个位于程序作用域外的原因引发,比如资源不足
标准异常类(standard exception classes
)
语言本身或者标准程序库所抛出的所有异常,都派生自基类exception
。类层次结构如下:
带着可以分为三组:
- 语言本身支持的异常
- C++标准程序库发出的异常
- 程序作用域之外的异常
所用头文件
#include <exception>
#include <new>
#include <typeinfo>
#include <ios>
#include <stdexcept>
#include <future>
针对[语言支持]而设计的异常类
此类异常用以支撑某些语言特性。所以,它们不是标准库的一部分,而是核心语言的一部分
如果以下操作失败,就会抛出这一类的异常:
- 运行期间,当
dynamic_cast
失败时,抛出bad_cast
异常 - 运行期
RTTI
过程中,如果交给typeid操作符的实参是0或者空指针,typeid操作符会bad_typeid
异常 bad_exception
是用来处理非预取的异常的。它可以用函数unexpected()抛出,该函数会在某个函数抛出的异常不再异常明细内时被调用。然而注意,C++11其不再鼓励使用异常明细
针对[逻辑错误]而设计的异常类
这些异常总是派生自logic_error
。所谓的[逻辑错误]就是应该在程序中避免的错误。C++标准程序库提供以下逻辑错误类别:
invalid_argument
表示无效参数length_error
指出某些行为“可能超过了最大极限”。比如传入的字符串长度太长out_of_range
指出参数值“不在预期范围内”。比如vector超过索引范围domain_error
指出专业领域范围内的错误。- 自C++11其,
future_error
用来指出当使用非同步系统调用时发生的逻辑差错。注意,此范围内的运行期差错是一般由class system_error发出
针对[运行期错误]而设计的异常类
派生自runtime_error
,用来指出“不在程序范围内,而且不容易回避”的事件
range_error
:内部计算时发生区间错误overflow_error
:算术运算时发生上溢位underflow_error
:算术运算时发生下溢位- 自C++11开始,system_error用来指出因底层操作系统而发生的错误。C++标准库可能在并发环境中抛出这个异常
new
操作失败,bad_alloc
异常,除非用的是new的nothrow版本- 针对标准程序库的IO部分,提供一个特殊的异常类
ios_base::failure
。当数据流由于错误或由于到达文件尾端而发生状态改变时,就可能抛出这个异常
异常类的成员
what()、code():描述异常信息
是什么
对所有标准异常类,what可以用来获取“类型以外的附加信息”,它返回一个以null结尾的byte string
class exception
{
public:
virtual const char* what() const noexcept;
}
使用示例
#include <iostream>
using namespace std;
int main(int argc,char *argv[]){
try {
throw std::runtime_error("Out of memory");
} catch (runtime_error &e) {
printf("%srn", e.what());
}
return 0;
}
差错码 和 差错状态
是什么?
和它类似的是,有个叫做 差错码 和 差错状态的东西也可以获取错误详情。
- 差错码(error code):用来封装errno值。比如errno.h中有很多系统差错编号,尝尝是一些枚举值
- 错误状态(error condition)是一种“抽象的可以移植的差错描述”对象
针对异常,C++标准库有时候给出差错码,有时候给出差错对象。C++标准库分别为差错码和差错对象提供了两个不同的类型:class std::error_code和std std::error_condition,它们本质上都是枚举类。我们来看下它是怎么用的
怎么用?
int main(){
std::error_condition edtion;
if(edtion == std::errc::invalid_argument){
}else if(edtion == std::future_errc::future_already_retrieved){
}
std::error_code ecode;
if(ecode == std::io_errc::stream){
}else if(ecode == std::errc::io_error){
}
}
另外,所有异常类比如std::system_error提供了非虚成员函数code(),它会返回一个** std::error_code**对象
class system_error : public std::runtime_error
{
public:
virtual const char* what() const noexcept;
const error_code& code() const noexcept ;
};
class future_error : public std::logic_error
{
public:
virtual const char* what() const noexcept;
const error_code& code() const noexcept ;
};
而std::error_code(本质是一个class)提供了成员函数,可以获得具体的错误细节
struct error_code
{
public:
int value() const noexcept;
const error_category& category() const noexcept ;
string message() const ;
explicit operator bool() const noexcept;
error_condition default_error_condition(int __i) const noexcept;
.....
};
为什么要这样设计呢?
- 因为不同的程序库可以对不同的差错码使用相同的整数值。所以,每个差错都有一个分类和一个值。只有在某个分类之内,每个值才有其明确意义
- message() 会有一个相应的信息,这个信息一般是what()为所有异常查出的信息的一部分
- operator bool ()返回一个真假值,表示是否有个差错码被设置(0表示无错误)。当有异常被捕获时,这个成员函数返回true
- default_error_condition()会返回相应的error_condition,而error_condition又提供了 category()、message()、operator bool()等
struct error_condition
{
int value() const noexcept;
const error_category& category() const noexcept ;
string message() const ;
explicit operator bool() const noexcept;
....
};
- category()会返回相应的error_category,而error_category又提供了如下接口:
- name(),返回分类名称
- message(int),返回传入值的信息
- **default_error_condition(int __i) **:默认差错状态
- 然后还有一些比较操作符
/// error_category
class error_category
{
public:
virtual const char* name() const noexcept;
virtual string message(int) const = 0;
virtual error_condition default_error_condition(int __i) const noexcept;
bool operator<(const error_category& __other) const noexcept;
bool operator==(const error_category& __other) const noexcept;
bool operator!=(const error_category& __other) const noexcept;
}
C++标准库提供了如下分类名称:
每一个分类都有响应的全局函数,返回分类:
因此,对于每一个差错码对象,我们都可以检查它是不是IO failure:
ios_base::failure e("aa");
if( e.code().category() == std::iostream_category()){
}
使用示例
template <typename T>
void processCodeException (const T& e)
{
using namespace std;
auto c = e.code();
cerr << "- category: " << c.category().name() << endl;
cerr << "- value: " << c.value() << endl;
cerr << "- msg: " << c.message() << endl;
cerr << "- def category: "
<< c.default_error_condition().category().name() << endl;
cerr << "- def value: "
<< c.default_error_condition().value() << endl;
cerr << "- def msg: "
<< c.default_error_condition().message() << endl;
}
其他成员
其他成员,用的很少,只是分组生成、复制、赋值、销毁等操作
使用
抛出异常
抛出标准异常
理论
(1)可以抛出标准异常
- 所有“提供了what()接口”的逻辑异常类和运行异常类,都只有一个构造函数接受std::string和一个构造函数接受**const char *,这些文件都将称为what()**的返回值。比如:
- std::system_error比较特别,它还提供的构造函数还包括一个差错码、一个what() string,和一个可有可无的分类等等
- 为了提供一个error_code对象,标准库引入了辅助函数make_error_code(…)
(2)不可以抛出基础的exception异常,也不可以抛出任何对语言支持而设计的异常,比如bad_cast、bad_typeid、bad_exception
使用
throw std::out_of_range("std::out_of_range");
throw std::system_error(std::make_error_code(std::errc::invalid_argument));
throw std::system_error(std::make_error_code(std::errc::invalid_argument), "invalid_argument");
从标准异常类别(exception classes
)中派生新异常[待补充]
还可以在程序中自定义一个异常类,这个类必须直接或者间接的继承exception,同时还必须确保其虚函数**what()和code()**可用
#include <iostream>
using namespace std;
class InterruptException : public std::runtime_error {
public:
InterruptException() : std::runtime_error("InterruptException") {}
~InterruptException() {}
};
static double quotient()
{
throw InterruptException(); // terminate function
return 1.1;
}
int main(int argc,char *argv[]){
try {
double result = quotient();
std::cout << "The quotient is: " << result << std::endl;
} catch (InterruptException& divideByZeroException) {
std::cout << "Exception occurred: " << divideByZeroException.what() << std::endl; // Exception occurred: attempted to divide by zero
}
}
class exception_prt:延后处理异常
C++11起,可以将异常存储到类型为exception_prt的对象中,稍后处理
#include <exception>
#include <system_error>
#include <future>
#include <iostream>
std::exception_ptr expr;
void foo(){
try {
throw std::out_of_range("std::out_of_range");
} catch (...) {
expr = std::current_exception();
}
}
void bar(){
if(expr != nullptr){
std::rethrow_exception(expr);
}
}
int main(){
foo();
bar();
}
- std::current_exception():返回一个std::exception_ptr对象,执行当前正被处理的异常。该异常会保持有效,直到没有任何std::exception_ptr 指向它
- std::rethrow_exception(expr):重新抛出异常
其他
构造函数、析构函数的异常处理
- C++标准规定,构造函数失败,析构函数不会执行。就是说在构造函数抛出异常前分配的资源将无法释放。
- 所以构造函数如果抛出异常前,申请了资源,需要自己释放。
- C++标准指明析构函数不能、也不应该抛出异常。
- 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
- 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
参考
最后
以上就是端庄烧鹅为你收集整理的C/C++编程:错误(error)处理和异常(exception)处理C++标准库是怎么对待异常的?标准异常类(standard exception classes)异常类的成员使用其他的全部内容,希望文章能够帮你解决C/C++编程:错误(error)处理和异常(exception)处理C++标准库是怎么对待异常的?标准异常类(standard exception classes)异常类的成员使用其他所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复