❥关于C++之异常

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了❥关于C++之异常相关的知识,希望对你有一定的参考价值。

首先阅读:有关异常和错误处理的新式 C++ 最佳做法https://docs.microsoft.com/zh-cn/cpp/cpp/errors-and-exception-handling-modern-cpp

throw异常try-catch捕获: 

// error3.cpp -- using an exception
#include <iostream>
double hmean(double a, double b);
int main() 
    double x, y, z;
    std::cout << "Enter two numbers: ";
    while (std::cin >> x >> y) 
        try 
            z = hmean(x,y);
         catch (const char * s) 
            std::cout << s << std::endl;
            std::cout << "Enter a new pair of numbers: ";
            continue;
                               // end of handler
        std::cout << "Harmonic mean of " << x << " and " << y
            << " is " << z << std::endl;
        std::cout << "Enter next set of numbers <q to quit>: ";
    
    std::cout << "Bye!\\n";
    return 0;

double hmean(double a, double b) 
    if (a == -b)
        throw "bad hmean() arguments: a = -b not allowed";
    return 2.0 * a * b / (a + b); 

将对象用作异常类型:

// exc_mean.h  -- exception classes for hmean(), gmean()
#include <iostream>
class bad_hmean 
private:
    double v1;
    double v2;
public:
    bad_hmean(double a = 0, double b = 0) : v1(a), v2(b)
    void mesg();
;
inline void bad_hmean::mesg()    
    std::cout << "hmean(" << v1 << ", " << v2 <<"): "
              << "invalid arguments: a = -b\\n";


class bad_gmean 
public:
    double v1;
    double v2;
    bad_gmean(double a = 0, double b = 0) : v1(a), v2(b)
    const char * mesg();
;
inline const char * bad_gmean::mesg()   
    return "gmean() arguments should be >= 0\\n";

//error4.cpp – using exception classes
#include <iostream>
#include <cmath>
#include "exc_mean.h"
// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
int main() 
	using std::cout;
	using std::cin;
	using std::endl;
	double x, y, z;
	cout << "Enter two numbers: ";
	while (cin >> x >> y) 
		try 
			z = hmean(x, y);
			cout << "Harmonic mean of " << x << " and " << y
				 << " is " << z << endl;
			cout << "Geometric mean of " << x << " and " << y
				 << " is " << gmean(x, y) << endl;
			cout << "Enter next set of numbers <q to quit>: ";
		 catch (bad_hmean& bg) 
			bg.mesg();
			cout << "Try again.\\n";
			continue;
		 catch (bad_gmean& hg) 
			cout << hg.mesg();
			cout << "Values used: " << hg.v1 << ", "
				 << hg.v2 << endl;
			cout << "Sorry, you don't get to play any more.\\n";
			break;
		
	
	cout << "Bye!\\n";
	return 0;

double hmean(double a, double b) 
	if (a == -b)
		throw bad_hmean(a, b);
	return 2.0 * a * b / (a + b);

double gmean(double a, double b) 
	if (a < 0 || b < 0)
		throw bad_gmean(a, b);
	return std::sqrt(a * b);

C++98中的异常规范(C++11中已将其摒弃):

double harm(double a) throw(bad_thind);// may throw bad_thing exception
double marm(double) throw();// doesn't throw an exception

异常规范的作用之一是,告诉用户可能需要使用try块。然而,这项工作也可使用注释轻松地完成。异常规范的另一个作用是,让编译器添加执行运行阶段检查的代码,检查是否违反了异常规范。这很难检查。例如,marm( )可能不会引发异常,但它可能调用一个函数,而这个函数调用的另一个函数引发了异常。另外,您给函数编写代码时它不会引发异常,但库更新后它却会引发异常。总之,编程社区(尤其是尽力编写安全代码的开发人员)达成的一致意见是,最好不要使用这项功能。而C++11也建议您忽略异常规范。

C++11确实支持一种特殊的异常规范:您可使用新增的关键字noexcept指出函数不会引发异常: 

double marm() noexcept;// marm() doesn't throw an exception

知道函数不会引发异常有助于编译器优化代码。通过使用这个关键字,编写函数的程序员相当于做出了承诺。

引发异常时编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的是引用。

class problem  ... ;
...
void super() throw (problem) 
    ...
    if (oh_no) 
        problem opps;// contruct object
        throw oops;// throw it
    
    ...

...
try 
    super();
 catch (problem& p) 
    // statements

p将指向oops的副本而不是oops本身。这是件好事,因为函数super()执行完毕后,oops将不复存在。顺便说一句,将引发异常和创建对象组合在一起将更简单:

throw problem();// construct and throw default problem object

可以一次抛出多个异常:

double Argh(double, double) throw(out_of_bounds, bad_exception);

 

你可能会问,既然throw语句将生成副本,为何代码中使用引用呢?毕竟,将引用作为返回值的通常原因是避免创建副本以提高效率。答案是,引用还有另一个重要特征:基类引用可以执行派生类对象。假设有一组通过继承关联起来的异常类型,则在异常规范中只需列出一个基类引用,它将与任何派生类对象匹配。

catch块排列顺序:

如果有一个异常类继承层次结构,应这样排列catch块:将捕获位于层次结构最下面的异常类的catch语句放在最前面,将捕获基类异常的catch语句放在最后面。

class bad_1 ...;
class bad_2 : public bad_1 ...;
class bad_3 : public bad_2 ...;
...
void duper() 
    ...
    if (oh_no)
        throw bad_1();
    if (rats)
        throw bad_2();
    if (drat)
        throw bad_3();

...
try 
    duper();
 catch(bad_3& be) 
    // statements
 catch(bad_2& be) 
    // statements
 catch(bad_1& be) 
    // statements

如果将bad_1 &处理程序放在最前面,它将捕获异常bad_1、bad_2和bad_3;通过按相反的顺序排列,bad_3异常将被bad_3 &处理程序所捕获。

假设你编写了一个调用另一个函数的函数,而你并不知道被调用的函数可能引发哪些异常。在这种情况下,仍能够捕获异常,即使不知道异常的类型。方法是使用省略号来表示异常类型,从而捕获任何异常:

catch (...)  // statements // catch any type exception

 可以将上述捕获所有异常的catch块放在最后面,这有点类似于switch语句中的default:

try 
    duper();
 catch(bad_3& be) 
    // statements
 catch(bad_2& be) 
    // statements
 catch(bad_1& be) 
    // statements
 catch (...) // catch whatever is left
    // statements

可以创建捕获对象而不是引用的处理程序。在catch语句中使用基类对象时,将捕获所有的派生类对象,但派生特性将被剥去,因此将使用虚方法的基类版本。

exception类:

<exception>头文件(以前为exception.h或except.h)定义了exception类,C++可以把它用作其他异常类的基类。代码可以引发exception异常,也可以将exception类用作基类。有一个名为what()的虚拟成员函数,它返回一个字符串,该字符串的特征随实现而异。然而,由于这是一个虚方法,因此可以在从exception派生而来的类中重新定义它:

virtual const char* what() const noexcept;
#include <exception>
class bad_hmean : public std::exception 
public:
    const char* what()  return "bad arguments to hmean()"; 
    ...
;
class bad_gmean : public std::exception 
public:
    const char* what()  return "bad arguments to gmean()"; 
    ...
;

如果不想以不同的方式处理这些派生而来的异常,可以在同一个基类处理程序中捕获它们:

try 
    ...
 catch(std::exception& e) 
    cout << e.what() << endl;
    ...

否则,可以分别捕获它们。

stdexcept异常类:

头文件<stdexcept>定义了其他几个异常类。

它分为逻辑错误运行时错误两大类,它们的类层次结构如下:

————————————————————————

逻辑异常:logic_error描述了典型的逻辑错误。

数学函数有定义域(domain)和值域(range)。定义域由参数的可能取值组成,值域由函数可能的返回值组成。例如:如果你编写一个函数,该函数将一个参数传递给函数std::sin(),则可以让该函数在参数不在定义域−1到+1之间时引发domain_error异常。

异常invalid_argument指出给函数传递了一个意料外的值。例如:如果函数希望接受一个这样的字符串:其中每个字符要么是‘0’要么是‘1’,则当传递的字符串中包含其他字符时,该函数将引发invalid_argument异常。

异常length_error用于指出没有足够的空间来执行所需的操作。例如:string类的append()方法在合并得到的字符串长度超过最大允许长度时,将引发length_error异常。

异常out_of_bounds通常用于指示索引错误。例如:你可以定义一个类似于数组的类,其operator() [ ]在使用的索引无效时引发out_of_bounds异常。

运行时异常:runtime_error异常系列描述了可能在运行期间发生但难以预计和防范的错误。

下溢(underflow)错误在浮点数计算中。一般而言,存在浮点类型可以表示的最小非零值,计算结果比这个值还小时将导致下溢错误。整型和浮点型都可能发生上溢错误,当计算结果超过了某种类型能够表示的最大数量级时,将发生上溢错误。计算结果可能不再函数允许的范围之内,但没有发生上溢或下溢错误,在这种情况下,可以使用range_error异常。

一般而言,logic_error系列异常表明存在可以通过编程修复的问题,而runtime_error系列异常表明存在无法避免的问题。

bad_alloc异常和new:

对于使用new导致的内存分配问题,C++的最新处理方式是让new引发bad_alloc异常。头文件<new>包含bad_alloc类的声明,它是从exception类公有派生而来的。但在以前,当无法分配请求的内存量时,new返回一个空指针。

// newexcp.cpp -- the bad_alloc exception
#include <iostream>
#include <new>
#include <cstdlib>  // for exit(), EXIT_FAILURE
using namespace std;
struct Big 
	double stuff[20000];
;
int main() 
	Big* pb;
	try 
		cout << "Trying to get a big block of memory:\\n";
		pb = new Big[10000]; // 1,600,000,000 bytes
		cout << "Got past the new request:\\n";
	 catch (bad_alloc& ba) 
		cout << "Caught the exception!\\n";
		cout << ba.what() << endl;
		exit(EXIT_FAILURE);
	
	cout << "Memory successfully allocated\\n";
	pb[0].stuff[0] = 4;
	cout << pb[0].stuff[0] << endl;
	delete[] pb;
	return 0;

 很多代码都是在new在失败时返回空指针时编写的。为处理new的变化,有些编译器提供了一个标记(开关),让用户选择所需的行为。当前,C++标准提供了一种在失败时返回空指针的new,其用法如下:

int* pi = new (std::nothrow) int;
int* pa = new (std::nowthrow) int[500];

使用这种new,可将上述程序核心代码改为如下:

Big* pb;
...
pb = new (std::nothrow) Big[10000];
if (pb == 0) 
    cout << "Could not allocate memory. Bye.\\n";
    exit(EXIT_FAILURE);

以上是关于❥关于C++之异常的主要内容,如果未能解决你的问题,请参考以下文章

❥关于C++之异常

15Python之异常处理

Java基础之异常

关于java中Exception异常

关于throwthrowstry--catch的问题

Python全栈自动化系列之Python编程基础(异常捕获)