C++基础与深度解析 | 异常处理 | 枚举与联合 | 嵌套类与局部类 | 嵌套名字空间与匿名名字空间 | 位域与volatile关键字

文章目录

一、异常处理

异常处理用于处理程序在调用过程中的非正常行为。

  • 传统的处理方法:传返回值表示函数调用是否正常结束。

    例如,返回 0 表示成功,非 0 表示失败。这种方法的缺点是函数的返回值被错误处理逻辑占用,不能用于其他目的。

    这种方法有局限性

  • C++ 中的处理方法:通过关键字 try/catch/throw 引入异常处理机制

    通过 try/catch/throw 引入了结构化的错误处理机制,使得错误处理逻辑与正常逻辑分离,提高了代码的可读性和可维护性。

    C++异常处理的关键组件

    • throw关键字

      throw 关键字用于抛出一个异常。它后面可以跟任意类型的表达式,该表达式的结果将被用作异常对象

    • try块

      try块包含了可能会抛出异常的代码。如果 try 块中的代码抛出了异常,那么与之匹配的 catch 块将被执行。

    • catch块

      catch块用于捕获并处理异常。可以有多个 catch 块来捕获不同类型的异常。

    • 异常类

      C++标准库中定义了一些基本的异常类,如 std::exceptionstd::runtime_errorstd::logic_error

异常触发时的系统行为---栈展开

  • 抛出异常后续的代码不会被执行

    一旦抛出异常,throw 语句之后的代码将不会被执行。控制流会立即转移到异常处理机制。

  • 局部对象会按照构造相反的顺序自动销毁

    在栈展开过程中,局部对象(包括由 new 分配的对象)会按照它们构造的相反顺序自动销毁。这是为了保证资源的正确释放,防止内存泄漏。

  • 系统尝试匹配相应的 catch 代码段

    • 如果找到匹配的 catch 块:

      • 执行 catch 块中的异常处理逻辑。
      • 异常被"捕获"后,catch 块之后的代码会继续执行。
    • 如果没有找到匹配的 catch 块:

      • 栈展开会继续进行,直到找到匹配的 catch 块或者退出当前函数。

      • 如果当前函数中没有找到匹配的

        块,栈展开会继续,直到:

        • 找到一个匹配的 catch 块。

        • 达到 main 函数。

          如果在 main 函数之前的所有函数中都没有找到匹配的 catch 块,程序将退出 main 函数。如果程序在退出 main 函数之前没有捕获到异常,std::terminate 函数将被调用。这通常会导致程序立即终止。

异常对象

  • 系统会使用抛出的异常拷贝初始化一个临时对象,称为异常对象
  • 异常对象会在栈展开过程中被保留,并最终传递给匹配的 catch 语句

try / catch语句块

  • 一个 try 语句块后面可以跟一到多个 catch 语句块(至少跟一个)

  • 每个 catch 语句块用于匹配一种类型的异常对象

    可以有多个catch块,每个用于处理不同类型的异常。

  • catch 语句块的匹配按照从上到下进行

    catch块按照它们在代码中出现的顺序(从上到下)进行匹配。一旦找到匹配的异常类型,就执行相应的catch块,忽略后面的catch块。

  • 使用 catch(...) 匹配任意异常

    catch(...)是一个通用的异常捕获器,它可以捕获任何类型的异常,包括未被前面的catch块捕获的异常。

  • 在 catch 中调用 throw 继续抛出相同的异常

    catch块中,可以使用throw;(不带参数)来重新抛出当前捕获的异常,这将导致继续搜索外层catch块。

示例:

cpp 复制代码
#include <iostream>

struct Str{};
struct Base{};
struct Derive : Base{};

void f1()
{
    int x;
    Str obj;

    //throw Derive{}; //打印Derive exception is called in f2 
    throw Str{};  //打印exception is called in f2
}

void f2()
{
    int x2;
    Str obj2;

    try
    {
        f1();
    }
    catch(Derive& e)
    {
        std::cout << "Derive exception is called in f2 " << "\n";
    }
    catch(Base& e)
    {
        std::cout << "Base exception is called in f2 " << "\n";
    }
    catch(...)
    {
        std::cout << "exception is called in f2" << "\n";
        throw;  //重新抛出当前捕获的异常
    }

    std::cout << "other logic in f2.\n";
}

void f3()
{
    try
    {
       f2(); 
    }
    catch(Str& e)
    {
        std::cout << "exception is called in f2" << "\n";
    }
}

int main()
{
    f3();
}

在一个异常未处理完成时抛出新的异常会导致程序崩溃

  • 不要在析构函数或 operator delete 函数重载版本中抛出异常
  • 通常来说, catch 所接收的异常类型为引用类型

异常与构造、析构函数

  • 使用 function-try-block保护初始化逻辑

    在C++中,function-try-block允许你在函数的初始化列表和函数体中使用trycatch。这在构造函数中特别有用,因为它可以保护对象的初始化代码。

    示例:

    cpp 复制代码
    #include <iostream>
    
    struct Str
    {
        Str() { throw 100; }
    }
    
    class Resource {
    public:
        Resource() 
        try : m_str()
        {
    		 
        }
        catch(int)
        {
            std::cout << "Exception is catched at Resource::Resource" << std::endl;
            throw;
        }
    
    private:
        Str m_str;
    };
    
    int main()
    {
        try
        {
           Resource obj; 
        }
        catch(int)
        {
            std::cout << "Exception is catched at main" << std::endl;
        }
    }

    运行结果:

    cpp 复制代码
    Exception is catched at Resource::Resource
    Exception is catched at main
  • 在构造函数中抛出异常:

    • 已经构造的成员对象会被销毁

      如果在构造函数中抛出异常,已经构造的成员对象将按照它们构造的相反顺序自动销毁。

    • 类本身的析构函数不会被调用

      如果异常是在对象的构造过程中抛出的,并且没有被捕获,那么类的析构函数不会被调用。这是因为对象被视为未完全构造,因此析构函数不适用。

    • 局部对象的销毁

      如果对象是局部的(即在栈上),异常抛出时,局部对象会自动销毁,但不会调用其析构函数。

    • 动态分配对象的销毁

      如果对象是动态分配的(即使用new),在构造函数中抛出异常且未被捕获时,需要手动释放分配的内存,因为析构函数不会被调用。

描述函数是否会抛出异常

  • 如果函数不会抛出异常,则应表明,从而为系统提供更多的优化空间

    • C++ 98 的方式:
      • throw() :表明不会抛出异常
      • throw(int, char):表明可能抛出异常,显式给定了要抛出异常的类型
    • C++11 后的改进:
      • noexcept :表明不会抛出异常
      • noexcept(false):表明可能抛出异常,不需要显式给定会抛出哪种类型的异常
  • noexcept

    • 限定符:接收 false / true 表示是否会抛出异常
    • 操作符:接收一个表达式,根据表达式是否可能抛出异常返回 false/true
    • 在声明了 noexcept 的函数中抛出异常会导致 terminate 被调用,程序终止
    • 不作为函数重载依据,但函数指针、虚拟函数重写时要保持形式兼容

示例:

cpp 复制代码
#include <iostream>

void fun2()
{

}

void fun() noexcept(noexcept(fun2()))
{
    fun2();
}

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

二、枚举与联合

三、嵌套类与局部类

四、嵌套名字空间与匿名名字空间

五、位域与volatile关键字

相关推荐
爱吃喵的鲤鱼9 分钟前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
7年老菜鸡37 分钟前
策略模式(C++)三分钟读懂
c++·qt·策略模式
Ni-Guvara1 小时前
函数对象笔记
c++·算法
似霰1 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
芊寻(嵌入式)1 小时前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
獨枭1 小时前
C++ 项目中使用 .dll 和 .def 文件的操作指南
c++
霁月风1 小时前
设计模式——观察者模式
c++·观察者模式·设计模式
橘色的喵1 小时前
C++编程:避免因编译优化引发的多线程死锁问题
c++·多线程·memory·死锁·内存屏障·内存栅栏·memory barrier
何曾参静谧2 小时前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
AI街潜水的八角2 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习