异常处理、文件操作和智能指针

好的!让我们深入探讨 C++ 中三个至关重要的核心主题:异常处理文件操作智能指针。我将以工业级代码标准,结合底层原理和实战案例,带你彻底掌握这些技术。


第一部分:异常处理(Exception Handling)

1. 异常机制的核心价值

传统错误处理的缺陷

bool readFile(const char* path, char* buffer, size_t size) {
    FILE* file = fopen(path, "r");
    if (!file) return false; // 错误码层层传递
    
    if (fread(buffer, 1, size, file) != size) {
        fclose(file); // 容易忘记关闭
        return false;
    }
    
    fclose(file);
    return true;
}

问题

  • 错误信息传递困难
  • 资源释放容易遗漏
  • 多层嵌套代码难以维护

2. C++ 异常处理三要素

(1) 基本语法

try {
    // 可能抛出异常的代码
    if (error_condition) {
        throw std::runtime_error("Error message");
    }
} 
catch (const std::exception& e) {
    std::cerr << "Caught exception: " << e.what() << "\n";
} 
catch (...) {
    // 捕获所有未处理的异常
}

(2) 标准异常体系

std::exception  
├── logic_error
│   ├── invalid_argument
│   ├── domain_error
│   ├── length_error
│   ├── future_error (C++11)
│   └── bad_optional_access (C++17)
│  
├── runtime_error
│   ├── range_error
│   ├── overflow_error 
│   ├── underflow_error
│   ├── system_error (C++11)
│   └── bad_variant_access (C++17)
│  
├── bad_alloc 
├── bad_cast
├── bad_typeid
├── bad_exception
├── bad_function_call (C++11) 
├── bad_weak_ptr (C++11)
├── bad_array_new_length (C++11)
└── ...(其他特定异常)  

(3) 异常安全保证等级

等级 描述
基本保证 不泄露资源,对象处于有效状态
强保证 操作要么完全成功,要么保持原有状态(类似事务)
无抛出保证 承诺不抛出任何异常(移动操作、析构函数应尽量做到)

3. RAII 与异常安全的完美结合

class DatabaseConnection {
    sqlite3* db;
public:
    explicit DatabaseConnection(const std::string& path) {
        if (sqlite3_open(path.c_str(), &db) != SQLITE_OK) {
            throw std::runtime_error("Failed to open database");
        }
    }

    ~DatabaseConnection() noexcept {
        if (db) sqlite3_close(db);
    }

    // 禁用拷贝(避免双释放)
    DatabaseConnection(const DatabaseConnection&) = delete;
    DatabaseConnection& operator=(const DatabaseConnection&) = delete;

    // 启用移动语义
    DatabaseConnection(DatabaseConnection&& other) noexcept 
        : db(other.db) { other.db = nullptr; }
};

使用示例

try {
    DatabaseConnection conn("data.db");
    // 执行数据库操作...
} 
catch (const std::exception& e) {
    // 即使发生异常,conn 析构仍会正确关闭连接
}

第二部分:文件操作(File I/O)

1. 标准库文件流体系

功能描述
std::ifstream 输入文件流(读操作)
std::ofstream 输出文件流(写操作)
std::fstream 双向文件流(读写操作)

2. 文件操作最佳实践

(1) 安全打开文件

#include <fstream>
#include <system_error> // 包含 std::io_errc

void readFile(const std::string& path) {
    std::ifstream file(path, std::ios::binary);
    
    // 显式检查打开状态
    if (!file.is_open()) {
        throw std::system_error(
            std::make_error_code(std::io_errc::stream),
            "Failed to open file"
        );
    }

    // 设置异常掩码(文件错误时抛出异常)
    file.exceptions(std::ios::failbit | std::ios::badbit);
    
    try {
        // 读取文件内容...
    } 
    catch (const std::ios_base::failure& e) {
        // 处理具体 I/O 错误
        throw std::runtime_error("File read error: " + std::string(e.what()));
    }
}

(2) 二进制文件操作

struct Pixel {
    uint8_t r, g, b;
};

void writeImage(const std::string& path, const Pixel* data, size_t count) {
    std::ofstream file(path, std::ios::binary);
    if (!file) throw std::runtime_error("Open failed");

    // 直接写入二进制数据
    file.write(reinterpret_cast<const char*>(data), count * sizeof(Pixel));

    // 显式检查写入是否完整
    if (file.bad()) {
        throw std::runtime_error("Write failed");
    }
}

第三部分:智能指针(Smart Pointers)

1. 智能指针进化史

指针类型 C++ 标准 所有权模型 特点
auto_ptr C++98 转移所有权 已废弃,存在安全隐患
unique_ptr C++11 独占所有权 轻量、高效、支持自定义删除器
shared_ptr C++11 共享所有权 引用计数、线程安全
weak_ptr C++11 观测共享对象 解决循环引用问题

2. unique_ptr 深度解析

(1) 基础用法

#include <memory>

void processData() {
    // 创建独占指针
    std::unique_ptr<int[]> buffer = std::make_unique<int[]>(1024);
    
    // 自动管理内存
    buffer[0] = 42;
    
    // 转移所有权(移动语义)
    std::unique_ptr<int[]> new_owner = std::move(buffer);
    
    // 此时 buffer 为空指针
}

(2) 自定义删除器

// 用于 C 风格文件指针
struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp) fclose(fp);
    }
};

void useFile() {
    std::unique_ptr<FILE, FileDeleter> file(fopen("data.bin", "rb"));
    if (!file) throw std::runtime_error("Open failed");
    
    // 自动调用 FileDeleter 关闭文件
}

3. shared_ptrweak_ptr 的协同

(1) 共享所有权示例

class TreeNode {
public:
    std::shared_ptr<TreeNode> parent;
    std::vector<std::shared_ptr<TreeNode>> children;

    ~TreeNode() { 
        std::cout << "TreeNode destroyed\n"; 
    }
};

void createTree() {
    auto root = std::make_shared<TreeNode>();
    auto child = std::make_shared<TreeNode>();
    
    child->parent = root;      // 共享所有权
    root->children.push_back(child);
    
    // 离开作用域时自动释放
}

(2) 循环引用问题解决方案

class Device;
class Controller;

class Device {
public:
    std::weak_ptr<Controller> ctrl; // 使用 weak_ptr 打破循环
    
    ~Device() { 
        std::cout << "Device destroyed\n"; 
    }
};

class Controller {
public:
    std::shared_ptr<Device> device;
    
    ~Controller() { 
        std::cout << "Controller destroyed\n"; 
    }
};

void createSystem() {
    auto ctrl = std::make_shared<Controller>();
    auto dev = std::make_shared<Device>();
    
    ctrl->device = dev;
    dev->ctrl = ctrl; // 不会增加引用计数
    
    // 离开作用域时能正确析构
}

第四部分:综合实战——安全文件处理器

#include <iostream>
#include <fstream>
#include <memory>
#include <vector>

class FileProcessor {
    std::unique_ptr<std::vector<char>, void(*)(std::vector<char>*)> buffer;
    
public:
    explicit FileProcessor(const std::string& path) 
        : buffer(nullptr, [](auto p){ if(p) delete p; }) 
    {
        std::ifstream file(path, std::ios::binary | std::ios::ate);
        if (!file) throw std::runtime_error("Cannot open file");
        
        const auto size = file.tellg();
        file.seekg(0);
        
        auto* data = new std::vector<char>(size);
        if (!file.read(data->data(), size)) {
            delete data;
            throw std::runtime_error("Read failed");
        }
        
        buffer.reset(data);
    }

    void process() {
        if (!buffer) throw std::logic_error("No data loaded");
        // 处理缓冲区数据...
    }

    void saveTo(const std::string& path) {
        std::ofstream out(path, std::ios::binary);
        if (!out) throw std::runtime_error("Cannot create output file");
        
        if (!out.write(buffer->data(), buffer->size())) {
            throw std::runtime_error("Write failed");
        }
    }
};

int main() {
    try {
        FileProcessor processor("input.dat");
        processor.process();
        processor.saveTo("output.dat");
    } 
    catch (const std::exception& e) {
        std::cerr << "Fatal error: " << e.what() << "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

关键知识点总结表

主题 核心要点 最佳实践
异常处理 - 使用 RAII 保证异常安全
- 优先使用标准异常类型
- 避免在析构函数抛异常
- 为自定义异常继承 std::exception
- 保持异常消息简明
文件操作 - 检查文件打开状态
- 显式指定二进制模式
- 使用 RAII 管理文件句柄
- 用 std::filesystem 处理路径(C++17)
- 大文件使用内存映射
智能指针 - 优先使用 unique_ptr
- shared_ptr 用于共享所有权
- weak_ptr 解决循环引用
- 使用 make_shared/make_unique
- 避免裸指针与智能指针混用

通过这个系统化的讲解,你应该已经掌握:

  1. 如何构建异常安全的健壮代码
  2. 高效可靠的文件操作方法
  3. 智能指针的正确使用场景

下一步建议:

  1. 使用 AddressSanitizer 检测内存错误
  2. 研究标准库源码(如 std::shared_ptr 的引用计数实现)
  3. 实践复杂资源管理场景(如数据库连接池)
posted @ 2025-03-22 15:26  無碍  阅读(53)  评论(0)    收藏  举报