C++语言学习(十八)——异常处理

C++语言学习(十八)——异常处理

一、C语言异常处理

异常是指程序在运行过程中产生可预料的执行分支。如除0操作,数组访问越界、要打开的文件不存在。
Bug是指程序中的错误,是不被预期的运行方式。如野指针、堆空间使用结束未释放。
C语言中处理异常的方式一般是使用if....else...分支语句。

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;

    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        cout << "a is  devieded by zero" <<endl;
    }
    return ret;
}

C语言通过setjmp和longjmp对异常处理进行优化。
int setjmp(jmp_buf env);
将上下文保存到jmp_buf结构体中
void longjmp(jmp_buf env, int value);
从jmp_buf结构体中恢复setjmp保存的上下文,最终从setjmp函数调用点返回,返回值为value。

#include <iostream>
#include <csetjmp>

using namespace std;

static jmp_buf env;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;

    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        longjmp(env, 1);
    }
    return ret;
}

int main(int argc, char *argv[])
{
    if( setjmp(env) == 0 )
    {
        double r = divide(1, 1);
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divided by zero..." << endl;
    }

    return 0;
}

二、C++语言异常处理机制

1、异常处理简介

C++语言中内置了异常处理的语法,try.....catch......。
try语句块用来处理正常代码逻辑,catch语句块用来处理异常处理情况,throw抛出异常。在try语句块抛出的异常在相应的catch语句块捕获处理。
同一个try语句块可以对应多个catch语句块,catch语句块可以定义具体处理的异常类型,不同的类型的异常由不同的catch语句块处理,try语句块可以抛出任何类型的异常,catch(...)用于处理所有类型的异常,任何异常都只能被捕获一次。
throw抛出的异常必须被catch处理,如果当前函数能够处理异常,继续执行;如果当前函数不能处理异常,函数停止执行并返回。未被处理的异常会顺着函数调用栈向上传递,直到被处理为止,否则程序将停止执行。
异常处理的匹配:
A、异常抛出后从上到下严格匹配每个catch语句块处理的类型,不能进行任何类型转换。
B、catch(...)语句块只能放到catch语句块分支的最后位置。
异常处理的使用实例:

    try
    {
        throw ‘c‘;
    }
    catch(char c)
    {
        cout << "catch(char c)" << endl;
    }
    catch(short c)
    {
        cout << "catch(short c)" << endl;
    }
    catch(double c)
    {
        cout << "catch(double c)" << endl;
    }
    catch(...)
    {
        cout << "catch(...)" << endl;
    }

catch语句块捕获的异常重新解释后可以抛出异常,抛出的异常在外层的try...catch中捕获。

    try
    {
        try
        {
            throw ‘c‘;
        }
        catch(int i)
        {
            cout << "Inner: catch(int i)" << endl;
            throw i;
        }
        catch(...)
        {
            cout << "Inner: catch(...)" << endl;
            throw;
        }
    }
    catch(...)
    {
        cout << "Outer: catch(...)" << endl;
    }

通常在catch语句块中捕获的异常重新解释后可以再次抛出异常,工程实践中通常用于统一异常类型,如通过捕获第三方库函数中抛出的异常,重新解释后抛出统一的异常处理信息。
异常的类型可以是自定义类型,自定义类型的异常匹配依旧是自上而下严格匹配,但由于赋值兼容性原则在异常匹配中适用,所以匹配子类异常的catch语句块放在catch分支的上部,匹配父类异常的catch语句块放在catch分支的下部。

#include <iostream>

using namespace std;

class Parent
{
public:
    Parent(int i):code(i)
    {

    }
private:
    int code;
};

class Child : public Parent
{
public:
    Child(int i):Parent(i),code(i)
    {

    }
private:
    int code;
};

int main(int argc, char *argv[])
{
    try
    {
        Child child(1);
        throw child;
    }
    catch(const Child& e)
    {
        cout << "catch(const Child& e)" << endl;
    }
    catch(const Parent& e)
    {
        cout << "catch(const Parent& e)" << endl;
    }

    return 0;
}

2、STL中的异常处理

STL提供了实用的异常处理类,STL中的异常都是从exception类继承而来,exception类只要有两个分支,logic_error和runtime_error。logic_error用于处理程序中可避免逻辑错误,runtime_error用于处理程序中无法处理的恶性错误。

#include <iostream>
#include <stdexcept>

using namespace std;

int main(int argc, char *argv[])
{
    int array[5] = {0};
    for(int i = 0; i < 5; i++)
    {
        array[i] = i;
    }
    try
    {
        for(int i = 0; i < 10; i++)
        {
            if(i >= 5)
            {
                throw out_of_range("out of range");
            }
            else
            {
                cout << array[i] <<endl;
            }
        }
    }
    catch(const out_of_range& e)
    {
        cout << e.what() << endl;
    }
    return 0;
}

3、try...catch特殊语法

try...catch语句用于分隔正常功能代码与异常处理代码。try...catch语句也可以将函数体分隔为两部分。
函数声明和定义时可以直接指定可能抛出的异常类型,异常声明作为函数的一部分可以提高代码可读性。
函数异常声明是一种与编译器之间的契约,函数声明异常后就只能抛出声明的异常。如果抛出其它异常将会导致程序运行终止。也可以通过函数异常声明定义无异常函数。

#include <iostream>

using namespace std;
//声明抛出的异常类型为int
void func(int i, int j)throw(int)
{
    if(0 < j && j < 10)
    {

    }
    else
    {
        throw 0;
    }
}

void test(int i)try
{
    func(i,i);
}
catch(int i)
{
    cout << "catch(int i): " << i << endl;
}
catch(...)
{
    cout << "Exception:" << endl;
}

int main(int argc, char *argv[])
{
    test(10);
    test(1);
    return 0;
}

上述代码中,func函数声明了抛出的异常类型为int,因此func函数只能抛出int类型异常,如果抛出其它类型异常将导致程序运行终止。即使test函数可以对抛出的其它类型异常进行捕获,程序也会运行终止。
如果函数内部可能会抛出多种类型的异常,需要在函数声明异常时指定声明的异常类型,代码如下:

#include <iostream>
#include <string>

using namespace std;
//声明抛出的异常类型为int,char,string
void func(int i, int j)throw(int,char,string)
{
    if(0 < j && j < 10)
    {
        throw j;
    }
    if(10 < j && j < 100)
    {
        throw ‘A‘;
    }
    else
    {
        throw string("string exception.");
    }
}

void test(int i)try
{
    func(i,i);
}
catch(int i)
{
    cout << "catch(int i): " << i << endl;
}
catch(char c)
{
    cout << "Exception:" << c << endl;
}
catch(string s)
{
    cout << s << endl;
}
catch(...)
{
    cout << "Exception:" << endl;
}

int main(int argc, char *argv[])
{
    test(115);//string exception.
    test(1);//catch(int i): 1
    test(20);//Exception:A
    return 0;
}

上述代码中,func函数可以抛出多种类型的异常,test函数会捕获func函数抛出的多种异常类型。

4、未被处理的异常

如果异常没有被处理,terminate函数会被自动调用。terminate函数是整个程序释放系统资源的最后机会。默认情况下,terminate函数调用abort库函数终止程序。abort函数使得程序执行异常而立即退出。

#include <iostream>

using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test()" << endl;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

int main(int argc, char *argv[])
{
    static Test test;
    throw 1;
    return 0;
}

上述代码运行结果如下:

C++支持使用自定义的terminate函数实现替换默认的terminate函数实现。
自定义terminate函数的实现规则如下:
A、自定义一个无返回值、无参数的函数
B、不能抛出任何异常
C、必须以某种方式结束当前程序
通过调用set_terminate函数可以设置自定义的terminate结束函数,其用法如下:
A、参数类型为void (*)()
B、返回值为默认的terminate函数入口地址

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test()" << endl;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

void terminate_test()
{
    cout << "void terminate_test()" << endl;
    exit(1);
}

int main(int argc, char *argv[])
{
    set_terminate(terminate_test);
    static Test test;
    throw 1;
    return 0;
}
// output:
// Test()
// void terminate_test()
// ~Test()

上述代码在最终terminate_test结束函数中调用了exit(1),exit函数会确保程序中全局、静态数据区的对象被正确销毁。如果使用abort函数替换exit函数,程序运行结果如下:

析构函数中抛出异常可能会导致最终结束函数terminate函数会被重复调用。

5、函数的异常规格说明

C++语言提供用于声明函数抛出异常的语法声明。异常声明作为函数声明的修饰符,位于函数参数表的后面。函数异常声明的示例如下:

//可能抛出任何异常
void func1();
//只能抛出的异常类型:char,int
void func2() throw(char, int);
//不抛出任何异常
void func3() throw();

函数异常声明的意义如下:
A、提示函数调用者必须做好异常处理的准备
B、提示函数的维护者不要抛出其它异常
C、函数异常规格说明是函数接口的一部分
如果函数抛出的异常类型不在函数异常声明中,全局unexpected()函数会被调用。默认的unexpected()函数会调用全局的terminate函数,可以自定义函数替换默认的unexpected()函数实现。
自定义的unexpected()函数的实现规则如下:
A、自定义一个无返回值、无参数的函数
B、能够再次抛出异常,当异常符合触发函数的异常规格说明时,恢复程序执行。否则,调用全局terminate函数结束程序。
通过调用set_unexpected函数可以设置自定义的unexpected()函数,用法如下:
A、参数类型为void (*)()
B、返回值为默认的unexpected()函数入口地址。

#include <iostream>
#include <cstdlib>

using namespace std;

void func() throw(int)
{
    cout << "void func()throw(int)" << endl;
    throw ‘A‘;
}

void unexpected_test()
{
    cout << "void unexpected_test()" << endl;
    throw 1;
}

int main(int argc, char *argv[])
{
    set_unexpected(unexpected_test);
    try
    {
        func();
    }
    catch(int)
    {
        cout << "catch(int)" << endl;
    }
    catch(char)
    {
        cout << "catch(char)" << endl;
    }

    return 0;
}
// output:
// void func()throw(int)
// void unexpected_test()
// catch(int)

C++编译器不一定对C++语言中函数异常规格说明进行支持。VC++编译器不支持,G++编译器支持。

6、动态内存申请异常

C语言中,malloc函数申请内存失败时返回NULL值。
C++语言中,对于早期的C++编译器,new关键字申请内存失败时,返回NULL值;对于现代C++编译器,new关键字申请内存失败时,抛出std::bad_alloc异常。
C++语言规范中,new关键字的标准行为如下:
A、new在内存分配时,如果空间不足,会调用全局的new_handler函数,new_handler函数中抛出std::bad_alloc异常;如果成功,会在分配的空间调用构造函数创建对象,并返回对象的地址。
B、可以自定义new_handler函数,处理默认new内存分配失败的情况。

#include <iostream>
#include <cstdlib>

using namespace std;

void new_handler_test()
{
    cout << "void new_handler_test()" << endl;
    cout << "No enough memory" << endl;
    exit(1);
}

int main(int argc, char *argv[])
{
    set_new_handler(new_handler_test);
    int* p = new(std::nothrow) int[10000000000];

    return 0;
}
// output:
// void new_handler_test()
// No enough memory

上述代码中,自定义new_handler函数,抛出异常时会调用。

#include <iostream>
#include <cstdlib>
#include <new>

using namespace std;

void new_handler_test()
{
    cout << "void new_handler_test()" << endl;
    cout << "No enough memory" << endl;
    exit(1);
}

int main(int argc, char *argv[])
{
    new_handler func = set_new_handler(new_handler_test);
    cout << "func = " << func << endl;
    if(func)
    {
        try
        {
            func();
        }
        catch(const bad_alloc& e)
        {
            cout << e.what() << endl;
        }
    }
    return 0;
}
// func = 0

上述代码是在G++编译器、VC++编译器下编译执行后打印的结果,表明G++编译器、VC++编译器没有设置默认的new_handler函数。如果C++编译器(如BCC编译器)设置有默认的new_handler函数,func函数执行时将会抛出bad_alloc异常,被捕获后打印出bad_alloc异常的相关信息。
不同的C++编译器,new关键字申请动态内存失败时表现不同。
工程实践中,为了在不同C++编译器间统一new关键字的行为,提高代码的可移植性,解决方案如下:
A、重新定义全局的new/delete实现,不抛出任何异常;自定义new_handler函数,不抛出任何异常(不推荐)。
B、在类内重载new/delete操作符,不抛出任何异常。
C、单次动态内存分配时使用nothrow参数,指明new不抛出异常。

#include <iostream>
#include <cstdlib>
#include <new>

using namespace std;

class Test
{
    int m_data;
public:
    Test()
    {
        cout << "Test()" << endl;
        m_data = 0;//异常
    }

    ~Test()
    {
        cout << "~Test()" << endl;
    }

    void* operator new (unsigned int size)
    {
        cout << "operator new: " << size << endl;
        // return malloc(size);
        return NULL;
    }

    void operator delete (void* p)
    {
        cout << "operator delete: " << p << endl;
        free(p);
    }

    void* operator new[] (unsigned int size)
    {
        cout << "operator new[]: " << size << endl;
        // return malloc(size);
        return NULL;
    }

    void operator delete[] (void* p)
    {
        cout << "operator delete[]: " << p << endl;
        free(p);
    }
};

int main(int argc, char *argv[])
{
    Test* p = new Test();
    cout << p << endl;
    delete p;

    return 0;
}
// output:
// operator new: 4
// Test()
// 异常

上述代码在执行new操作符函数后会调用Test构造函数,并在初始化m_data成员变量时抛出异常。为了确保不同C++编译器在调用new关键字时具有相同的行为,需要在new失败时不抛出异常,因此需要在new操作符增加函数的异常声明。

#include <iostream>
#include <cstdlib>
#include <new>

using namespace std;

class Test
{
    int m_data;
public:
    Test()
    {
        cout << "Test()" << endl;
        m_data = 0;//异常
    }

    ~Test()
    {
        cout << "~Test()" << endl;
    }

    void* operator new (unsigned int size) throw()
    {
        cout << "operator new: " << size << endl;
        // return malloc(size);
        return NULL;
    }

    void operator delete (void* p)
    {
        cout << "operator delete: " << p << endl;
        free(p);
    }

    void* operator new[] (unsigned int size) throw()
    {
        cout << "operator new[]: " << size << endl;
        // return malloc(size);
        return NULL;
    }

    void operator delete[] (void* p)
    {
        cout << "operator delete[]: " << p << endl;
        free(p);
    }
};

int main(int argc, char *argv[])
{
    Test* p = new Test();
    cout << p << endl;
    delete p;
    p = new Test[5];
    cout << p << endl;
    delete [] p;

    return 0;
}
// output:
// operator new: 4
// 0
// operator new[]: 24
// 0

上述代码对Test类的new和delete关键字进行了重载,统一了new失败时的行为。

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    //不抛出异常
    int* p = new(nothrow) int[1000000000];
    cout << "p = " << p << endl;
    delete [] p;
    int array[2] = {0};
    struct Test
    {
        int x;
        int y;
    };
    //在栈空间创建对象
    Test* pTest = new(array) Test();
    pTest->x = 100;
    pTest->y = 200;
    cout << array[0] << endl;
    cout << array[1] << endl;
    //显示析构
    pTest->~Test();

    return 0;
}
// output:
// p = 0
// 100
// 200

上述代码中使用nothrow关键字对象new进行限制,确保new创建对象失败时不会抛出异常。new关键字也可以指定创建对象的地址空间,比如栈空间。

7、C++编译器对new关键字的实现

不是所有的C++编译器都遵循C++标准规范,C++编译器可能重新定义new关键字的实现,并在实现中抛出bad_alloc异常。VC++编译器对new关键字进行了重定义,new关键字在new.cpp文件中进行了实现。

#ifdef _SYSCRT
#include <cruntime.h>
#include <crtdbg.h>
#include <malloc.h>
#include <new.h>
#include <stdlib.h>
#include <winheap.h>
#include <rtcsup.h>
#include <internal.h>

void * operator new( size_t cb )
{
    void *res;

    for (;;) {

        //  allocate memory block
        res = _heap_alloc(cb);

        //  if successful allocation, return pointer to memory

        if (res)
            break;

        //  call installed new handler
        if (!_callnewh(cb))
            break;

        //  new handler was successful -- try to allocate again
    }

    RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));

    return res;
}
#else  /* _SYSCRT */

#include <cstdlib>
#include <new>

_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;
                _RAISE(nomem);
                }

        return (p);
        }

/*
 * Copyright (c) 1992-2002 by P.J. Plauger.  ALL RIGHTS RESERVED.
 * Consult your license regarding permissions and restrictions.
 V3.13:0009 */
#endif  /* _SYSCRT */

上述代码显示,在new失败时默认抛出bad_alloc异常。

原文地址:http://blog.51cto.com/9291927/2164586

时间: 2024-11-05 02:18:20

C++语言学习(十八)——异常处理的相关文章

C++语言学习(八)——操作符重载

C++语言学习(八)--操作符重载 一.操作符重载基础 1.操作符重载的语法 通过operator关键字可以定义特殊的函数,operator本质是通过函数重载操作符. Type operator operatorname(const Type p1, const Type p2) { Type ret; return ret; } 2.友元函数重载操作符 可以将操作符重载函数声明为友元函数. #include <iostream> using namespace std; class Comp

Java基础学习笔记十八 异常处理

什么是异常?Java代码在运行时期发生的问题就是异常. 在Java中,把异常信息封装成了一个类.当出现了问题时,就会创建异常类对象并抛出异常相关的信息(如异常出现的位置.原因等). 异常的继承体系 在Java中使用Exception类来描述异常. 查看API中Exception的描述,Exception 类及其子类是 Throwable 的一种形式,它用来表示java程序中可能会产生的异常,并要求对产生的异常进行合理的异常处理. Exception有继承关系,它的父类是Throwable.Thr

android学习十八(Service服务的基本用法)

定义一个服务 在项目中定义一个服务,新建一个ServiceTest项目,然后在这个项目中新增一个名为MyService的类,并让它继承自Service,完成后的代码如下所示: package com.jack.servicetest; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class MyService extends Service { @Over

C语言学习入门 (八) 结构体和枚举

结构体 它允许内部的元素是不同类型的 结构体的定义 //结构体类型: struct Person { char name[20]; //char *name; int age; float weight; }; //定义一个结构体变量,定义变量时才分配存储空间 structPerson person;    //与类型定义一起使用 struct Student {  //结构体类型与变量同时定义 char *name; int age; } stu; struct {  //省略类型,结构体变量

Go语言学习(十二)面向对象编程-结构体

1.结构体的初始化方式 例如自定义一个结构体 package main import( "fmt" ) type Rect struct{ //type和struct为关键字 x,y float64 //结构体成员 widh,height float64 } func (r *Rect) Area() float64{ return r.width * r.height } func main(){ //初始结构体的几种方式: rect1 := new(Rect) rect2 := &

python学习笔记(八):异常处理

一.异常处理 在程序运行过程中,总会遇到各种各样的错误.程序一出错就停止运行了,那我们不能让程序停止运行吧,这时候就需要捕捉异常了,通过捕捉到的异常,我们再去做对应的处理. 下面我们先写一个函数,实现除法运算. 1 2 3 4 5 6 7 8 9 10 11 12 def calc(a,b): return a/b print(calc(5,1))#调用,没有错误,结果是5.0 >>> 5.0 print(calc(5,0))#再次调用,这时候就不对了,因为被除数不能为0,下面报了一堆错

百分点认知智能实验室出品:深度迁移学习十八问

编者按 深度迁移学习是基于深度神经网络的迁移学习方法,BERT通过预训练模型达到深度迁移学习的效果,自从2018年底BERT横空出世以来,就以势不可挡的姿态横扫了众多榜单,甚至在阅读理解任务SQuAD 中超越人类水平.BERT在公检法.媒体出版.军工.快消零售等工业界也迅速落地,如百分点智能对话系统.百分点智能审校系统和百分点智能翻译系统等.BERT几乎在所有的下游任务中效果都获得了明显提升,BERT自此开创了一个NLP的新时代,那就是pre-train + fine-tuning的时代. 基于

Java学习十八

学习内容: 1.Java集合 1.自定义的set类添加重复数据需要在实体类中添加hashcode和equals方法. 2.查找set对象信息(以宠物猫为例) //在集合中查找花花的信息并输出 if(set.contains(huahua)){ system.out.println("花花找到了!"); system.out.println(huahua); }else{ system.out.println("花花没找到!"); } //在集合中使用名字查找花花的信

CSS基础学习十八:CSS布局之浮动

CSS布局中说到定位就不得不提浮动,浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动 框的边框为止.由于浮动框不在文档的普通流中,所以文档的普通流中的块框表现得就像浮动框不存在一样. 一float属性的定义和用法 float属性:设置元素浮动 可能的值: none 不浮动,在文档流内,默认 left  左浮动,脱离文档流 right 右浮动,脱离文档流 inherit 规定应该从父元素继承 float属性的值. float属性定义元素在哪个方向浮动.以往这个属性总应用于图像,使文

Java学习(十八):二叉树的三种递归遍历

二叉树的三种递归遍历: 1 public class StudentNode 2 { 3 private String name; 4 5 private StudentNode leftNode; 6 7 private StudentNode rightNode; 8 9 public String getName() 10 { 11 return name; 12 } 13 14 public void setName(String name) 15 { 16 this.name = na