C++异常处理

简介: C++异常处理

栈解旋

 

异常抛出后,从进入try块起,到异常被处理,这期间在栈上的构造的所有对象都会被自动析构。析构的顺序与构造的顺序相反。这一过程叫做解旋。

【也就是说栈模型先进后出并没有被打破,当异常throw抛掷,栈上的对象会被析构】

#include <iostream>
using namespace std;
class MyException{};
class Test
{
public:
    Test(int a=0,int b=0)
    {
        this->a = a;
        this->b = b;
        cout<<"构造函数被执行"<<endl;
    }
    void printT()
    {
        cout<<"a:"<<a<<"  "<<"b:"<<b<<endl;
    }
    ~Test()
    {
        cout<<"Test 析构函数执行"<<"a:"<<a<<"b:"<<b<<endl;
    }
private:
    int a;
    int b;
};
//一旦声明了抛出的类型,就只能抛出这些声明了的异常
//一般情况下,为了增强程序的可读性,在函数声明的时候列出所有的异常类型
void myFunc()throw(MyException)
{
    Test t1;
    Test t2(1,2);
    cout<<"定义了两个变量,异常抛出后测试栈变量如何被析构"<<endl;
    throw MyException();
}
int main(void)
{
    try
    {
        myFunc();
    }
    catch(MyException)
    {
        cout<<"正在处理异常类型 MyException"<<endl;
    }
    catch(...)
    {
        cout<<"未知异常类型"<<endl;
    }
    return 0;
}

异常接口声明

1.为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型。

  例如:void func() throw(A,B,C,D)这个函数能够且只能抛出类型ABCD及其子类型的异常

2.如果在函数声明中没有包含异常接口声明,则此函数可以抛掷任何类型的异常

3.一个不抛出任何异常的的函数可以声明为

 void func throw();

4.如果一个函数抛出了它的异常接口声明所不允许的异常,unexpected函数会被调用,该函数默认行为调用terminate函数终止程序


异常类型和异常变量的生命周期

1) throw的异常是有类型的,可以是数字、字符串、类对象...

2) throw的异常是有类型的,catch严格按照类型进行匹配

#include <iostream>
using namespace std;
//throe int
void filecopy02(char *filename2,char *filename1)
{
    FILE *fp1 = nullptr;
    FILE *fp2 = nullptr;
    fp1 = fopen(filename1,"rb");
    if(fp1 == nullptr)
    {
        throw 1;
    }
    fp2 = fopen(filename2,"wb");
    if(fp2 == nullptr)
    {
        throw 2;
    }
    char buf[256];
    int readlen,writelen;
    readlen=fread(buf,1,256,fp1);
    while(!feof(fp1))
    {
        writelen = fwrite(buf,1,readlen,fp2);
        if(readlen != writelen)
        {
            throw 3;
        }
    }
    fclose(fp1);
    fclose(fp2);
}
//throw string
void filecopy03(char *filename2,char *filename1)
{
    FILE *fp1 = nullptr;
    FILE *fp2 = nullptr;
    fp1 = fopen(filename1,"rb");
    if(fp1 == nullptr)
    {
        throw "打开源文件失败";
    }
    fp2 = fopen(filename2,"wb");
    if(fp2 == nullptr)
    {
        throw "打开目标文件失败";
    }
    char buf[256];
    int readlen,writelen;
    readlen=fread(buf,1,256,fp1);
    while(!feof(fp1))
    {
        writelen = fwrite(buf,1,readlen,fp2);
        if(readlen != writelen)
        {
            throw "文件拷贝过程失败";
        }
    }
    fclose(fp1);
    fclose(fp2);
}
//throw class
class BadSrcFile
{
public:
    void toString()
    {
        cout<<"aaaaaa"<<endl;
    }
};
class BadDestFile
{
};
class BadCpyFile
{
};
void filecopy04(char *filename2,char *filename1)
{
    FILE *fp1 = nullptr;
    FILE *fp2 = nullptr;
    fp1 = fopen(filename1,"rb");
    if(fp1 == nullptr)
    {
        throw BadSrcFile();
    }
    fp2 = fopen(filename2,"wb");
    if(fp2 == nullptr)
    {
        throw BadDestFile();
    }
    char buf[256];
    int readlen,writelen;
    readlen = fread(buf,1,256,fp1);
    while (!feof(fp1))
    {
        writelen = fwrite(buf,1,readlen,fp2);
        if(writelen != readlen)
        {
            throw BadCpyFile();
        }
    }
    fclose(fp1);
    fclose(fp2);
}
int main(void)
{
    try
    {
        filecopy02("01.txt","02.txt");
    }
    catch(int e)
    {
       cout<<"发生异常:"<<e<<endl;
    }
    catch(const char *e)
    {
        cout<<"发生异常:"<<e<<endl;
    }
    catch(BadSrcFile *e)
    {
        e->toString();
        cout<<"发生异常,打开源文件失败"<<endl;
    }
    catch(BadSrcFile &e)
    {
        e.toString();
         cout<<"发生异常,打开源文件失败"<<endl;
    }
    catch(BadDestFile e)
    {
        cout<<"发生异常,打开目的文件失败"<<endl;
    }
    catch(BadCpyFile e)
    {
        cout<<"拷贝文件时发生异常"<<endl;
    }
    catch(...)
    {
        cout<<"未知异常..."<<endl;
    }
    return 0;
}

C++编译器通过throw来产生对象,C++编译器在执行对应的catch分支,相当于一个函数应用,把实参传递给形参。【当然会通过解螺旋把栈清空】

不妨把catch看作一个模板函数,会进行严格的类型匹配


异常的层次结构(继承在异常中的应用)

  • 异常是类(自己创建的异常类)
  • 异常派生
  • 异常中的数据:数据成员
  • 按引用传递异常
  • 在异常中使用虚函数
class eSize
{
public:
    eSize(int index)
    {
        this->index = index;
    }
    virtual void printErr()
    {
    } 
private:
    int index;
};
class eNegative:public eSize
{
public:
    eNegative(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index<0"<<endl;
    } 
}; 
class eZero:public eSize
{
public:
    eZero(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index=0"<<endl;
    } 
}; 
class eTooBig:public eSize
{
public:
    eTooBig(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index>10000"<<endl;
    } 
}; 
class eTooSmall:public eSize
{
public:
    eTooSmall(int x):eSize(x)
    {
    }
     virtual void printErr()
    {
        cout<<"index<10"<<endl;
    } 
};

标准程序库异常

       C++标准提供了一组标准异常类,这些类以基类Exception开始,标准程序库抛出的所有异常,都派生于该基类,这些类构成如下图的异常类的派生继承关系。该基类提供一个成员函数what()用于返回错误信息(返回类型为const char*)。在Exception类中,what()函数声明如下:

       virtual const char * what()const throw();

上面包含了各个具体异常类的含义以及它们的头文件。runtime_error和logic_error是一些具体的异常类的基类,它们分别表示两大类异常。logic_error表示那些可以在程序中被预先检测到的异常,也就是说如果小心得编写程序,这些类异常能够避免,而runtime_error则表示那些难以被预先检测的异常。

一些编程语言规定只能抛掷某个类的派生类(例如java中允许抛掷的类必须派生自Exception类),C++虽然没有这项强制的要求,但仍然可以选择这样实践。例如:在程序中可以使得所有抛出的异常皆派生自Exception(或者直接抛出标准程序库提供的异常类型,或者从标准程序库提供的异常类派生出新的类),这样会带来很多方便.(因为多态)

logic_error和runtime_error两个类及其派生类,都有一个接收const string &型参数的改造函数。在构造异常对象时需要将具体的错误信息传递给该函数,如果调用该对象的what函数,就可以得到构造时提供的错误信息。

#include <iostream>
#include <stdexcept>
using namespace std;
class Teacher
{
public:
    Teacher(int age)
    {
        if(age > 100)
        {
            throw out_of_range("年龄太大");
        }
        this->age = age;
    }
private:
    int age;
};
class Dog
{
public:
    Dog()
    {
        p = new int[1024*1024*100];  //4MB
    }
private:
    int *p;
};
int main(void)
{
    // try
    // {
    //     Teacher t1(30);
    // }
    // catch(exception &e)
    // {
    //     cout<<e.what()<<endl;
    // }
    try
    {
        Dog *pdog;
        for(int i=1;i<=10000000;i++)
        {
            pdog = new Dog();
        }
    }
    catch(exception& e)
    {
        std::cerr << e.what() << '\n';
    }
    return 0;
}
相关文章
|
3月前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
74 2
|
6月前
|
安全 编译器 程序员
【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用(一)
【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用
93 1
|
6月前
|
安全 C++
C++中的异常处理与错误处理机制
C++中的异常处理与错误处理机制
73 0
|
5月前
|
C++
C++一分钟之—异常处理try-catch
【6月更文挑战第22天】C++异常处理机制,借助`try`、`catch`、`throw`管理错误,优雅处理异常,防止程序崩溃。`try`包围可能出错的代码,`catch`捕获异常,`throw`引发异常。基本结构是:`try-catch`块中,未捕获的异常将向上抛出。多`catch`块可按顺序捕获不同类型的异常。易错点包括忽视异常传播、不精确的`catch`和资源未清理。通过精确捕获、RAII技术和适当的异常策略,提升代码健壮性和效率。
41 1
|
5月前
|
C++
C++核心技术要点《异常处理详解》
C++核心技术要点《try-throw-catch异常处理详解》
51 2
|
5月前
|
程序员 编译器 C++
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
47 0
|
6月前
|
程序员 编译器 C++
C++中的异常处理:技术详解与实践
C++中的异常处理:技术详解与实践
112 1
|
5月前
|
C++
C++对C的改进和拓展\异常处理
C++对C的改进和拓展\异常处理
35 0
|
6月前
|
C++
C++程序异常处理
C++程序异常处理
41 1
|
5月前
|
C++
Essential C++ 第7章 异常处理
Essential C++ 第7章 异常处理