异常处理、文件操作和智能指针
好的!让我们深入探讨 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_ptr 与 weak_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- 避免裸指针与智能指针混用 |
通过这个系统化的讲解,你应该已经掌握:
- 如何构建异常安全的健壮代码
- 高效可靠的文件操作方法
- 智能指针的正确使用场景
下一步建议:
- 使用 AddressSanitizer 检测内存错误
- 研究标准库源码(如
std::shared_ptr的引用计数实现) - 实践复杂资源管理场景(如数据库连接池)

浙公网安备 33010602011771号