【C++篇】让错误被温柔对待(下):异常高级特性与最佳实践

文章目录

    • C++异常机制详解(二):高级特性与最佳实践
    • 一、异常的重新抛出
      • [1.1 为什么需要重新抛出](#1.1 为什么需要重新抛出)
      • [1.2 重新抛出的语法](#1.2 重新抛出的语法)
      • [1.3 实战案例:消息发送重试机制](#1.3 实战案例:消息发送重试机制)
      • [1.4 throw vs throw e](#1.4 throw vs throw e)
    • 二、异常安全问题
      • [2.1 什么是异常安全](#2.1 什么是异常安全)
      • [2.2 异常导致的资源泄漏](#2.2 异常导致的资源泄漏)
      • [2.3 解决方案一:异常捕获后释放资源](#2.3 解决方案一:异常捕获后释放资源)
      • [2.4 解决方案二:RAII机制(推荐)](#2.4 解决方案二:RAII机制(推荐))
      • [2.5 析构函数中的异常](#2.5 析构函数中的异常)
    • 三、异常规范
      • [3.1 C++98的异常规范(已废弃)](#3.1 C++98的异常规范(已废弃))
      • [3.2 C++11的noexcept(推荐)](#3.2 C++11的noexcept(推荐))
      • [3.3 noexcept作为运算符](#3.3 noexcept作为运算符)
      • [3.4 什么时候使用noexcept](#3.4 什么时候使用noexcept)
    • 四、C++标准库异常体系
      • [4.1 标准异常继承体系](#4.1 标准异常继承体系)
      • [4.2 exception基类](#4.2 exception基类)
      • [4.3 常用标准异常](#4.3 常用标准异常)
      • [4.4 使用标准异常的好处](#4.4 使用标准异常的好处)
    • 五、异常使用的最佳实践
      • [5.1 何时使用异常](#5.1 何时使用异常)
      • [5.2 异常处理的建议](#5.2 异常处理的建议)
      • [5.3 性能考虑](#5.3 性能考虑)
    • 六、总结
      • [6.1 全系列回顾](#6.1 全系列回顾)
      • [6.2 核心要点](#6.2 核心要点)
      • [6.3 继续学习](#6.3 继续学习)

C++异常机制详解(二):高级特性与最佳实践

💬 欢迎讨论:本文是C++异常机制系列的第二篇,将深入探讨异常的高级特性和实践经验。如果你在学习过程中有任何疑问,欢迎在评论区留言交流!

👍 点赞、收藏与分享:这是系列的完结篇,建议结合第一篇一起学习。如果觉得有帮助,请分享给更多的朋友!

🚀 系列回顾:在第一篇中,我们学习了异常的基本概念、抛出与捕获、栈展开机制以及异常继承体系的设计。本篇将继续深入学习。


一、异常的重新抛出

1.1 为什么需要重新抛出

在实际开发中,我们可能需要这样的场景:

  1. 分类处理:捕获异常后,只处理某些特定类型,其他类型继续向上抛出
  2. 记录日志:捕获异常记录日志后,让上层继续处理
  3. 重试机制:某些错误需要重试,重试失败后再抛出
  4. 资源清理:捕获异常进行必要的清理后,重新抛出

1.2 重新抛出的语法

捕获异常后,直接使用throw;(不带任何参数)就可以重新抛出当前捕获的异常:

cpp 复制代码
try
{
    // 可能抛出异常的代码
}
catch (const exception& e)
{
    // 做一些处理
    cout << "记录日志: " << e.what() << endl;
    
    throw;  // 重新抛出捕获到的异常
}

注意throw;只能在catch块内使用,它会抛出当前正在处理的异常对象。

1.3 实战案例:消息发送重试机制

假设我们在开发一个即时通讯应用,发送消息时可能因为网络不稳定而失败。我们希望:

  1. 如果是网络问题,自动重试3次
  2. 如果重试3次后仍失败,抛出异常给上层
  3. 如果是其他错误(如对方已删除好友),直接抛出,不重试

实现代码

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;

// 定义HTTP异常
class HttpException : public exception
{
public:
    HttpException(const string& msg, int code)
        : _msg(msg)
        , _code(code)
    {}
    
    const char* what() const noexcept override
    {
        return _msg.c_str();
    }
    
    int getCode() const { return _code; }
    
private:
    string _msg;
    int _code;
};

// 模拟底层发送消息函数
void _SendMsg(const string& msg)
{
    int random = rand() % 10;
    
    if (random < 3)  // 30%概率网络不稳定
    {
        throw HttpException("网络不稳定,发送失败", 102);
    }
    else if (random == 9)  // 10%概率对方删除好友
    {
        throw HttpException("你已经不是对方的好友,发送失败", 103);
    }
    else
    {
        cout << "消息发送成功: " << msg << endl;
    }
}

// 带重试机制的发送函数
void SendMsg(const string& msg)
{
    const int MAX_RETRY = 3;
    
    for (int i = 0; i < MAX_RETRY + 1; ++i)
    {
        try
        {
            _SendMsg(msg);
            break;  // 发送成功,退出循环
        }
        catch (const HttpException& e)
        {
            // 如果是网络问题(错误码102),则重试
            if (e.getCode() == 102)
            {
                // 已经重试3次还是失败,重新抛出异常
                if (i == MAX_RETRY)
                {
                    cout << "重试" << MAX_RETRY << "次后仍然失败" << endl;
                    throw;  // 重新抛出
                }
                
                cout << "第" << i + 1 << "次重试..." << endl;
            }
            else  // 其他错误,直接抛出,不重试
            {
                throw;
            }
        }
    }
}

int main()
{
    srand(time(0));
    
    string msg;
    while (cin >> msg)
    {
        try
        {
            SendMsg(msg);
        }
        catch (const HttpException& e)
        {
            cout << "最终失败: " << e.what() << endl;
            cout << "错误码: " << e.getCode() << endl << endl;
        }
    }
    
    return 0;
}

运行示例

bash 复制代码
hello
消息发送成功: hello
world
第1次重试...
消息发送成功: world
test
第1次重试...
第2次重试...
第3次重试...
重试3次后仍然失败
最终失败: 网络不稳定,发送失败
错误码: 102

friend
最终失败: 你已经不是对方的好友,发送失败
错误码: 103

设计亮点

  1. 智能重试:只对网络错误重试,其他错误直接失败
  2. 重试限制:避免无限重试导致程序卡死
  3. 异常传播:重试失败后,异常继续向上传播
  4. 用户体验:提供详细的重试信息

1.4 throw vs throw e

很多初学者容易混淆throw;throw e;,它们有重要区别:

cpp 复制代码
try
{
    // ...
}
catch (const DerivedClass& e)
{
    // 方式1:重新抛出捕获到的原始异常对象(保持类型)
    throw;
    
    // 方式2:抛出e的拷贝(可能发生对象切片)
    throw e;
}

对比演示

cpp 复制代码
class Base
{
public:
    virtual void show() { cout << "Base" << endl; }
};

class Derived : public Base
{
public:
    void show() override { cout << "Derived" << endl; }
};

void test1()
{
    try
    {
        throw Derived();
    }
    catch (const Base& e)
    {
        e.show();  // 输出Derived
        throw;     // 重新抛出,保持Derived类型
    }
}

void test2()
{
    try
    {
        throw Derived();
    }
    catch (const Base& e)
    {
        e.show();    // 输出Derived
        throw e;     // 抛出Base类型!发生对象切片
    }
}

int main()
{
    cout << "test1:" << endl;
    try { test1(); }
    catch (const Base& e) { e.show(); }  // 输出Derived
    
    cout << "\ntest2:" << endl;
    try { test2(); }
    catch (const Base& e) { e.show(); }  // 输出Base!
    
    return 0;
}

输出

bash 复制代码
test1:
Derived
Derived

test2:
Derived
Base

结论 :使用throw;重新抛出,能够保持异常对象的真实类型,避免对象切片。


二、异常安全问题

2.1 什么是异常安全

异常安全是指:当异常发生时,程序能够正确处理资源,不会造成资源泄漏或数据不一致。

常见的异常安全问题:

  1. 内存泄漏:new了内存,异常导致delete没执行
  2. 文件句柄泄漏:打开文件后,异常导致没有关闭
  3. 锁没释放:获取锁后,异常导致锁没释放
  4. 数据不一致:修改到一半时抛出异常

2.2 异常导致的资源泄漏

问题代码

cpp 复制代码
void Func()
{
    int* array = new int[10];
    
    // 这里可能抛出异常
    // ...
    if (某个条件)
    {
        throw "error";
    }
    // ...
    
    delete[] array;  // 如果前面抛异常,这行不会执行!
}

这是一个典型的内存泄漏问题。如果在delete之前抛出异常,内存永远不会被释放。

2.3 解决方案一:异常捕获后释放资源

cpp 复制代码
double Divide(int a, int b)
{
    if (b == 0)
    {
        throw "Division by zero!";
    }
    return (double)a / b;
}

void Func()
{
    int* array = new int[10];
    
    try
    {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    }
    catch (...)
    {
        // 捕获异常后释放资源
        cout << "释放资源: delete[] " << array << endl;
        delete[] array;
        
        throw;  // 重新抛出异常,让外层处理
    }
    
    // 正常情况下也要释放
    cout << "正常释放: delete[] " << array << endl;
    delete[] array;
}

int main()
{
    try
    {
        Func();
    }
    catch (const char* errmsg)
    {
        cout << "错误: " << errmsg << endl;
    }
    
    return 0;
}

这种方案的问题

  1. 代码重复:资源释放代码出现两次
  2. 容易遗漏:如果有多个资源,每个都要这样处理
  3. 可维护性差:添加新资源时容易出错

2.4 解决方案二:RAII机制(推荐)

RAII(Resource Acquisition Is Initialization)是C++中处理资源的最佳方式。核心思想是:

  • 资源的获取在构造函数中完成
  • 资源的释放在析构函数中完成
  • 利用栈展开时自动调用析构函数的特性

智能指针示例

cpp 复制代码
void Func()
{
    // 使用智能指针,自动管理内存
    unique_ptr<int[]> array(new int[10]);
    
    int len, time;
    cin >> len >> time;
    cout << Divide(len, time) << endl;
    
    // 即使抛出异常,array的析构函数也会被调用
    // 内存自动释放,不会泄漏
}

自定义RAII类示例

cpp 复制代码
// 文件资源RAII封装
class FileGuard
{
public:
    FileGuard(const char* filename)
        : _file(fopen(filename, "r"))
    {
        if (!_file)
        {
            throw runtime_error("打开文件失败");
        }
        cout << "文件打开成功" << endl;
    }
    
    ~FileGuard()
    {
        if (_file)
        {
            fclose(_file);
            cout << "文件已关闭" << endl;
        }
    }
    
    FILE* get() { return _file; }
    
    // 禁止拷贝
    FileGuard(const FileGuard&) = delete;
    FileGuard& operator=(const FileGuard&) = delete;
    
private:
    FILE* _file;
};

void ProcessFile()
{
    FileGuard file("data.txt");
    
    // 使用文件
    // ...
    
    // 如果这里抛异常,file的析构函数仍会被调用
    // 文件会被正确关闭
    if (某个条件)
    {
        throw runtime_error("处理文件时出错");
    }
}

锁资源RAII封装

cpp 复制代码
#include <mutex>

class LockGuard
{
public:
    LockGuard(mutex& mtx) : _mtx(mtx)
    {
        _mtx.lock();
        cout << "锁已获取" << endl;
    }
    
    ~LockGuard()
    {
        _mtx.unlock();
        cout << "锁已释放" << endl;
    }
    
    LockGuard(const LockGuard&) = delete;
    
private:
    mutex& _mtx;
};

mutex g_mtx;

void CriticalSection()
{
    LockGuard lock(g_mtx);
    
    // 临界区代码
    // ...
    
    // 即使抛异常,锁也会被正确释放
}

2.5 析构函数中的异常

重要原则:析构函数不应该抛出异常!

原因

  1. 栈展开过程中调用析构函数,如果析构函数再抛异常,会导致terminate()被调用
  2. 一个对象的析构可能需要释放多个资源,如果中途抛异常,后续资源无法释放

错误示例

cpp 复制代码
class Resource
{
public:
    ~Resource()
    {
        if (释放失败)
        {
            throw runtime_error("释放失败");  // 危险!
        }
    }
};

void func()
{
    Resource r1, r2;
    throw "error";  // 栈展开会调用r2和r1的析构
}
// 如果r2的析构抛异常,程序直接terminate

正确做法

cpp 复制代码
class Resource
{
public:
    ~Resource() noexcept  // 明确标记不抛异常
    {
        try
        {
            // 清理资源
            cleanup();
        }
        catch (...)
        {
            // 吞掉异常,记录日志
            cerr << "析构时发生错误,但已被处理" << endl;
        }
    }
};

多资源释放的安全做法

cpp 复制代码
class MultiResource
{
public:
    ~MultiResource()
    {
        // 即使某个资源释放失败,也要继续释放其他资源
        try { delete _resource1; } catch(...) {}
        try { delete _resource2; } catch(...) {}
        try { close(_file); } catch(...) {}
        try { _mutex.unlock(); } catch(...) {}
    }
    
private:
    Resource* _resource1;
    Resource* _resource2;
    int _file;
    mutex _mutex;
};

三、异常规范

3.1 C++98的异常规范(已废弃)

C++98允许在函数声明后使用throw()指定可能抛出的异常类型:

cpp 复制代码
// 表示这个函数只会抛出bad_alloc异常
void* operator new(size_t size) throw(bad_alloc);

// 表示这个函数不会抛出异常
void* operator delete(size_t size, void* ptr) throw();

// 表示可能抛出多种异常
void func() throw(int, double, string);

为什么被废弃?

  1. 过于复杂,难以维护
  2. 编译器检查不严格
  3. 运行时检查有性能开销
  4. 实际使用中问题多多

3.2 C++11的noexcept(推荐)

C++11简化了异常规范,只提供两种状态:

  • 什么都不写:可能抛异常
  • noexcept:不会抛异常

基本用法

cpp 复制代码
// 承诺不会抛异常
void func1() noexcept
{
    // ...
}

// 可能会抛异常(默认行为)
void func2()
{
    // ...
}

编译器不会在编译时强制检查

cpp 复制代码
void func() noexcept
{
    throw runtime_error("error");  // 编译通过(可能有警告)
}

int main()
{
    try
    {
        func();  // 运行时会调用terminate(),程序终止
    }
    catch (...)
    {
        cout << "不会执行到这里" << endl;
    }
    
    return 0;
}

3.3 noexcept作为运算符

noexcept还可以作为运算符,检查一个表达式是否会抛异常:

cpp 复制代码
double Divide(int a, int b)
{
    if (b == 0)
        throw "Division by zero!";
    return (double)a / b;
}

int Add(int a, int b) noexcept
{
    return a + b;
}

int main()
{
    int i = 0;
    
    cout << noexcept(i++) << endl;          // 1,基本操作不抛异常
    cout << noexcept(Add(1, 2)) << endl;    // 1,noexcept函数
    cout << noexcept(Divide(1, 2)) << endl; // 0,可能抛异常
    cout << noexcept(Divide(1, 0)) << endl; // 0,函数声明未标记noexcept
    
    return 0;
}

输出

bash 复制代码
1
1
0
0

注意:noexcept运算符在编译期求值,它不会真正执行表达式,只是检查表达式是否被声明为noexcept。

3.4 什么时候使用noexcept

应该使用noexcept的场景

  1. 析构函数(默认就是noexcept)
cpp 复制代码
~MyClass() noexcept  // 明确标记
{
    // 清理资源
}
  1. 移动构造和移动赋值
cpp 复制代码
class MyClass
{
public:
    MyClass(MyClass&& other) noexcept
    {
        // 移动操作通常不应抛异常
    }
    
    MyClass& operator=(MyClass&& other) noexcept
    {
        // ...
        return *this;
    }
};

为什么移动操作应该noexcept?因为STL容器在扩容时,如果移动构造是noexcept的,就会使用移动;否则为了保证异常安全,会使用拷贝。

  1. swap函数
cpp 复制代码
void swap(MyClass& other) noexcept
{
    // swap操作不应该抛异常
}
  1. 不会失败的简单操作
cpp 复制代码
int size() const noexcept { return _size; }
bool empty() const noexcept { return _size == 0; }

不要滥用noexcept

如果不确定函数是否会抛异常,不要轻易加noexcept,因为违反承诺会导致程序终止。


四、C++标准库异常体系

4.1 标准异常继承体系

C++标准库定义了一套完整的异常继承体系,基类是std::exception

bash 复制代码
exception (基类)
├── logic_error (逻辑错误)
│   ├── invalid_argument
│   ├── domain_error
│   ├── length_error
│   ├── out_of_range
│   └── future_error
├── runtime_error (运行时错误)
│   ├── range_error
│   ├── overflow_error
│   ├── underflow_error
│   └── system_error
├── bad_alloc (内存分配失败)
├── bad_cast (类型转换失败)
├── bad_typeid
└── bad_exception

4.2 exception基类

cpp 复制代码
class exception
{
public:
    exception() noexcept;
    exception(const exception&) noexcept;
    exception& operator=(const exception&) noexcept;
    virtual ~exception() noexcept;
    
    virtual const char* what() const noexcept;
};

关键方法

  • what():返回异常描述字符串,是虚函数,派生类可以重写

4.3 常用标准异常

logic_error:逻辑错误

表示程序逻辑上的错误,理论上可以通过检查代码避免:

cpp 复制代码
#include <stdexcept>

// invalid_argument:无效参数
void setAge(int age)
{
    if (age < 0 || age > 150)
    {
        throw invalid_argument("年龄必须在0-150之间");
    }
    _age = age;
}

// out_of_range:越界访问
char& at(size_t index)
{
    if (index >= _size)
    {
        throw out_of_range("索引越界");
    }
    return _data[index];
}

// length_error:长度错误
void resize(size_t n)
{
    if (n > max_size())
    {
        throw length_error("超出最大长度");
    }
    // ...
}

runtime_error:运行时错误

表示运行时才能检测到的错误:

cpp 复制代码
// overflow_error:溢出
void compute()
{
    if (result > INT_MAX)
    {
        throw overflow_error("计算结果溢出");
    }
}

// 一般的运行时错误
void connect()
{
    if (!connected)
    {
        throw runtime_error("连接失败");
    }
}

bad_alloc:内存分配失败

cpp 复制代码
try
{
    int* p = new int[1000000000000];  // 分配巨大内存
}
catch (const bad_alloc& e)
{
    cout << "内存分配失败: " << e.what() << endl;
}

4.4 使用标准异常的好处

统一的接口

所有标准异常都继承自exception,可以统一捕获:

cpp 复制代码
int main()
{
    try
    {
        // 各种可能抛出标准异常的操作
        vector<int> v(5);
        v.at(10);  // 抛出out_of_range
    }
    catch (const exception& e)  // 捕获所有标准异常
    {
        cout << "异常: " << e.what() << endl;
    }
    
    return 0;
}

良好的语义

通过异常类型就能知道错误的大致类别,不需要查错误码。

可扩展性

可以继承标准异常类,添加自己的信息:

cpp 复制代码
class FileException : public runtime_error
{
public:
    FileException(const string& msg, const string& filename)
        : runtime_error(msg)
        , _filename(filename)
    {}
    
    const string& filename() const { return _filename; }
    
private:
    string _filename;
};

五、异常使用的最佳实践

5.1 何时使用异常

应该使用异常的场景

  1. 真正的错误情况
cpp 复制代码
void openFile(const string& filename)
{
    FILE* file = fopen(filename.c_str(), "r");
    if (!file)
    {
        throw runtime_error("无法打开文件: " + filename);
    }
}
  1. 构造函数中的错误

构造函数没有返回值,异常是报告错误的唯一方式:

cpp 复制代码
class Socket
{
public:
    Socket(const string& ip, int port)
    {
        _fd = connect(ip, port);
        if (_fd < 0)
        {
            throw runtime_error("连接失败");
        }
    }
};
  1. 跨越多层调用的错误传播
cpp 复制代码
// 底层函数
void lowLevelFunc()
{
    if (error)
        throw runtime_error("底层错误");
}

// 中间层函数(不需要处理,自动传播)
void midLevelFunc()
{
    lowLevelFunc();
}

// 高层函数(统一处理)
void highLevelFunc()
{
    try
    {
        midLevelFunc();
    }
    catch (const exception& e)
    {
        // 处理错误
    }
}

不应该使用异常的场景

  1. 正常的控制流程
cpp 复制代码
// 错误示例:用异常控制循环
try
{
    for (int i = 0; ; ++i)
    {
        if (i == 10)
            throw i;
    }
}
catch (int n)
{
    cout << n << endl;
}
  1. 高频操作

异常的开销比较大,不适合在高频调用的代码中使用:

cpp 复制代码
// 不好:在循环中频繁抛异常
for (int i = 0; i < 1000000; ++i)
{
    try
    {
        if (condition)
            throw "error";
    }
    catch (...)
    {
    }
}
  1. 可以通过返回值解决的简单情况
cpp 复制代码
// 简单情况用返回值更好
bool isValid(int x)
{
    return x >= 0 && x <= 100;
}

// 不需要这样
void checkValid(int x)
{
    if (x < 0 || x > 100)
        throw invalid_argument("无效值");
}

5.2 异常处理的建议

建议1:按引用捕获

cpp 复制代码
// 推荐:按const引用捕获
catch (const exception& e)
{
    cout << e.what() << endl;
}

// 不推荐:按值捕获(会发生对象切片)
catch (exception e)  // 切片!
{
}

建议2:从具体到一般的顺序

cpp 复制代码
try
{
    // ...
}
catch (const out_of_range& e)     // 具体异常
{
}
catch (const logic_error& e)      // 较一般的异常
{
}
catch (const exception& e)        // 最一般的异常
{
}
catch (...)                        // 最后的保底
{
}

建议3:main函数的保护

cpp 复制代码
int main()
{
    try
    {
        // 程序主逻辑
        return runApplication();
    }
    catch (const exception& e)
    {
        cerr << "程序异常: " << e.what() << endl;
        return 1;
    }
    catch (...)
    {
        cerr << "未知异常" << endl;
        return 2;
    }
}

建议4:记录日志

cpp 复制代码
catch (const exception& e)
{
    // 记录详细的错误信息
    logError("异常发生",
             "类型", typeid(e).name(),
             "信息", e.what(),
             "文件", __FILE__,
             "行号", __LINE__);
    
    // 然后决定是处理还是重新抛出
}

建议5:提供异常安全保证

设计类时,提供以下级别的异常安全保证之一:

  1. 不抛保证:承诺不抛异常(noexcept)
  2. 强保证:要么操作成功,要么对象状态不变
  3. 基本保证:如果抛异常,对象仍处于有效状态
  4. 无保证:抛异常后对象状态未定义(尽量避免)

5.3 性能考虑

异常的性能特点

  1. 正常路径开销小:如果不抛异常,现代编译器的实现几乎没有开销
  2. 异常路径开销大:抛出和捕获异常的开销比较大
  3. 结论:异常适合处理真正的异常情况,不适合正常控制流

性能测试

cpp 复制代码
#include <chrono>

// 使用异常
void testException(bool throwIt)
{
    if (throwIt)
        throw 1;
}

// 使用返回值
bool testReturn(bool error)
{
    return !error;
}

int main()
{
    using namespace std::chrono;
    const int COUNT = 1000000;
    
    // 测试无异常情况
    auto start = high_resolution_clock::now();
    for (int i = 0; i < COUNT; ++i)
    {
        try { testException(false); }
        catch(...) {}
    }
    auto end = high_resolution_clock::now();
    cout << "无异常: " 
         << duration_cast<milliseconds>(end - start).count() 
         << "ms" << endl;
    
    // 测试有异常情况
    start = high_resolution_clock::now();
    for (int i = 0; i < COUNT; ++i)
    {
        try { testException(true); }
        catch(...) {}
    }
    end = high_resolution_clock::now();
    cout << "有异常: " 
         << duration_cast<milliseconds>(end - start).count() 
         << "ms" << endl;
    
    // 测试返回值
    start = high_resolution_clock::now();
    for (int i = 0; i < COUNT; ++i)
    {
        testReturn(false);
    }
    end = high_resolution_clock::now();
    cout << "返回值: " 
         << duration_cast<milliseconds>(end - start).count() 
         << "ms" << endl;
    
    return 0;
}

六、总结

6.1 全系列回顾

通过两篇文章,我们系统学习了C++异常机制:

第一篇:基础篇

  • 异常的概念与优势
  • 异常的抛出与捕获
  • 栈展开机制
  • 异常匹配规则
  • 异常继承体系设计

第二篇:高级篇

  • 异常重新抛出技巧
  • 异常安全与RAII
  • 异常规范(noexcept)
  • 标准库异常体系
  • 最佳实践与性能考虑

6.2 核心要点

异常的本质

异常是一种结构化的错误处理机制,它将错误检测和错误处理分离,让代码更清晰、更易维护。

异常安全的关键

使用RAII技术管理资源,利用析构函数的自动调用来保证资源正确释放。

异常规范的现代实践

  • 析构函数默认noexcept
  • 移动操作建议noexcept
  • 其他函数按需标记

最佳实践总结

  1. 只在真正的异常情况使用异常
  2. 按const引用捕获异常
  3. 从具体到一般安排catch顺序
  4. 使用标准异常或继承标准异常
  5. 注意异常安全,使用RAII
  6. 析构函数不要抛异常

6.3 继续学习

要深入掌握异常机制,建议:

  1. 阅读经典书籍

    • 《Effective C++》
    • 《More Effective C++》
    • 《C++ Coding Standards》
  2. 学习标准库实现

    • 研究STL容器如何保证异常安全
    • 学习智能指针的RAII实现
  3. 实践项目

    • 在实际项目中应用异常处理
    • 设计自己的异常体系
    • 注重代码的异常安全性

通过这两篇文章的学习,我们全面掌握了C++异常机制。异常是现代C++不可或缺的特性,正确使用异常能让程序更加健壮和易于维护。希望这个系列对你有所帮助!

C++异常机制系列到此完结!感谢你的阅读,如果对你有帮助,记得点赞、收藏、分享!期待在评论区看到你的学习心得!❤️

相关推荐
没有bug.的程序员2 小时前
服务治理体系:从零到一的全景落地指南
java·开发语言·数据库·微服务·架构
kylezhao20192 小时前
C#上位机开发数据持久化:xml数据导入导出
xml·开发语言·c#
霜!!2 小时前
openssh升级
linux·运维·服务器
草莓熊Lotso2 小时前
2025年12月远程协作平台全景评测:智能连接时代的效率革命
运维·服务器·数据库
2501_909800812 小时前
Java多线程
java·开发语言·多线程
Rhys..2 小时前
Jenkins配置GitHub token教程
运维·github·jenkins·ci
小无名呀2 小时前
使用C语言连接MySQL
数据库·c++·mysql
weixin_462446232 小时前
Node.js 纯 JS 生成 SVG 练字纸(米字格 / 田字格)完整实现解析
开发语言·javascript·node.js
小趴菜不能喝2 小时前
Docker Swarm
运维·docker·容器