C++异常处理

异常处理机制

C++异常处理机制是一种用于处理程序运行过程中可能出现的异常情况的机制。它允许程序在异常情况发生时,将控制权从当前代码块转移到异常处理代码块,并执行适当的处理操作。异常处理机制的原理如下:

  1. 抛出异常:当程序运行时遇到错误或异常情况时,可以使用 throw 关键字来抛出一个异常。异常可以是任何类型的数据,通常是一个对象,但也可以是基本数据类型、指针或者枚举等。
  2. 异常传播:一旦异常被抛出,在当前函数内寻找合适的异常处理机制。如果当前函数内没有合适的异常处理代码,异常将会传播到调用该函数的上一层函数中,直到找到合适的异常处理代码或者传播到 main() 函数。
  3. 异常处理:异常处理代码位于 try-catch 块中,try 块用于包围可能抛出异常的代码,而 catch 块用于捕获并处理异常。当抛出的异常类型匹配 catch 块中指定的类型时,程序将转移到该 catch 块,并执行相应的异常处理代码。如果在 try 块中的代码抛出异常,但没有找到匹配的 catch 块,那么异常将继续传播到上一层的 try-catch 块,或者传播到调用栈的顶层(main() 函数),如果在这些位置仍然没有找到合适的异常处理代码,程序将终止并调用 std::terminate() 函数来终止程序的执行。
  4. 栈展开:异常处理机制在执行时会进行栈展开(stack unwinding),即在找到匹配的 catch 块之前,会销毁所有在 try 块中创建的对象。栈展开确保在异常发生时可以正确地释放资源,避免资源泄漏。
  5. 异常类型匹配:异常类型可以是基类或派生类,catch 块中的异常类型可以与抛出的异常类型匹配,也可以是其基类类型,这样可以捕获多个相关类型的异常。

异常处理示例

示例1,只抛出异常但不处理异常

#include <exception>
#include <string>
#include <iostream>

// throw exception
void Func(bool b)
{
    if( b == false)
    {
        throw std::string{"exception occured!"};
    }
}

void Call()
{
    Func(false);
}

int main()
{
    Call();
    std::cout << "excute over" << "\n";
}

结果:

terminate called after throwing an instance of 'std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >'
Program terminated with signal: SIGSEGV

main函数并没有返回,程序在异常发生处崩溃。
示例2,直接处理异常

#include <exception>
#include <string>
#include <iostream>

// throw exception
void Func(bool b)
{
    if( b == false)
    {
        throw std::string{"exception occured!"};
    }
}

void Call()
{
    try
    {
        Func(false);  
    }
    catch(std::string& s)
    {
        std::cout << s << "\n";  
    }
}

int main()
{
    Call();
    std::cout << "excute over" << "\n";
}

结果:

exception occured!
excute over

异常被检测并处理,main函数正常执行结束。
示例3,间接处理异常

#include <exception>
#include <string>
#include <iostream>

// throw exception
void Func(bool b)
{
    if( b == false)
    {
        throw std::string{"exception occured!"};
    }
}

void Call()
{
    Func(false); 
}

int main()
{
    try
    {
        Call();  
    }
    catch(std::string& s)
    {
        std::cout << s << "\n";  
    }
    std::cout << "excute over" << "\n";
}

结果:

exception occured!
excute over

结果与示例2一致,异常会函数调用栈一直传播直到能处理其为止,如果main函数也不能处理则调用std::terminate() 函数结束程序。

函数try块

函数 try 块是一种 函数体 的替代语法形式,它是函数定义的一部分。_函数 try 块_将一系列 catch 子句与整个函数体,以及成员初始化器列表(如果用于构造函数)关联起来。从函数体中的任何语句,或(对于构造函数)从任何成员或基类的构造函数,或(对于析构函数)从任何成员或基类的析构函数中抛出的所有异常,以与常规 try 块中抛出的异常时相同方式,将控制转移到处理块序列中。函数 try 块的主要目的是应对从构造函数中的成员初始化器列表抛出的异常,进行记录并重抛,修改异常对象并重抛,抛出一个不同的异常,或终止程序。析构函数或常规函数很少用到它们。

语法示例

void Func(bool b) try
{
    if(b == false)
    {
        throw -1;
    }
}
catch(int i)
{
    /*....*/
}

以上写法与如下写法等价:

void Func(bool b)
{
    try
    {
        if(b == false)
        {
            throw -1;
        }
    }
    catch(int i)
    {
        /*....*/
    }
}

:::info
Note:
函数 try 块不会捕获从按值传递的函数形参的复制/移动构造函数和析构函数中抛出的异常:这些异常是在调用方的语境抛出的。
:::

特殊之处

对于没有函数try块的普通函数而言,以下代码会出现变量重定义的编译错误:

void Func(bool b)
{
    // 声明一个和函数形参一样的变量
    bool b;
}

但是对于有函数try块的如下代码却并不会出现编译错误

void Func(bool b) try
{
    bool b;
}
catch(...)
{
    /*....*/
}

以上形式不好理解,转化为其等价形式就相对容易理解:

void Func(bool b)
{
    try
    {
        bool b;
        /*....*/
    }
    catch(...)
    {
        /*....*/
    }
}

如代码所示,因为函数形参btry块变量b定义在不同的作用域中,所以并不会出现变量名冲突的问题。

noexcept说明符

noexcept说明符用于指定函数是否抛出异常。用法示例如下:

// 函数不抛出异常
void Func() noexcept;
void Func() noexcept(true);

// 函数可能会抛出异常
void Func() noexcept(false);

在C++中的函数要么不抛出异常,要么有可能抛出异常。函数上的 noexcept 说明不是一种编译时检查;它只不过是程序员告知编译器函数是否可以抛出异常的一种方法。编译器能用此信息启用不会抛出的函数上的某些优化,以及启用能在编译时检查特定表达式是否声明为可抛出任何异常的 noexcept 运算符。例如,诸如 std::vector 的容器会在元素的移动构造函数是 noexcept 的情况下移动元素,否则就复制元素(除非复制构造函数不可访问,但有可能会抛出的移动构造函数只会在放弃强异常保证的情况下考虑)。
noexcept 说明符 (C++11 起) - cppreference.com

noexcept运算符

noexcept运算符进行编译时检查,在表达式声明不会抛出任何异常的情况下返回true
用法示例:

void Func()
{
    std::cout << "Hello world" << std::endl;
}

int main()
{
    std::cout << std::boolalpha << noexcept(Func()) <<std::endl;
}

以上代码的运行结果为:

false

noexcept运算符并不会运行Func()函数,是一个不求值表达式。

posted @ 2024-04-29 16:09  漫山华  阅读(13)  评论(0编辑  收藏  举报