在 C++ 中,RAII(资源获取即初始化)是一种重要的编程技巧,它通过对象的生命周期来管理资源,避免泄漏。下面以文件操作为例,演示如何使用 RAII 实现一个安全、易用且符合现代 C++ 风格的文件处理类。
1. 需求分析
- 打开文件后立即获取句柄,文件关闭时自动释放句柄。
- 支持读写操作,且错误信息能被用户捕获。
- 不需要用户手动调用 close(),避免忘记关闭导致的资源泄漏。
- 兼容 POSIX 与 Windows 两大平台。
2. 设计思路
- 创建一个
FileHandle类,在构造函数中打开文件,在析构函数中关闭文件。 - 把文件描述符(int on POSIX, HANDLE on Windows)包装成私有成员,防止外部直接操作。
- 提供
write()、read()等成员函数,内部使用系统调用完成实际操作。 - 对错误情况抛出
std::runtime_error或使用错误码返回,视情况而定。
3. 关键实现代码
// file_handle.hpp
#pragma once
#include <string>
#include <stdexcept>
#include <vector>
#include <system_error>
#ifdef _WIN32
#include <windows.h>
#else
#include <fcntl.h>
#include <unistd.h>
#endif
class FileHandle {
public:
enum Mode { Read, Write, Append };
FileHandle(const std::string& path, Mode mode) {
#ifdef _WIN32
DWORD dwDesiredAccess = 0;
DWORD dwCreationDisposition = 0;
if (mode == Read) {
dwDesiredAccess = GENERIC_READ;
dwCreationDisposition = OPEN_EXISTING;
} else if (mode == Write) {
dwDesiredAccess = GENERIC_WRITE;
dwCreationDisposition = CREATE_ALWAYS;
} else { // Append
dwDesiredAccess = FILE_APPEND_DATA;
dwCreationDisposition = OPEN_ALWAYS;
}
hFile = CreateFileA(path.c_str(), dwDesiredAccess,
FILE_SHARE_READ, nullptr,
dwCreationDisposition,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
throw std::system_error(GetLastError(), std::system_category(), "Failed to open file");
#else
int flags = 0;
if (mode == Read) flags = O_RDONLY;
else if (mode == Write) flags = O_WRONLY | O_CREAT | O_TRUNC;
else flags = O_WRONLY | O_CREAT | O_APPEND;
fd = open(path.c_str(), flags, 0666);
if (fd < 0)
throw std::system_error(errno, std::generic_category(), "Failed to open file");
#endif
}
// 禁止拷贝
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 移动构造与赋值
FileHandle(FileHandle&& other) noexcept {
#ifdef _WIN32
hFile = other.hFile;
other.hFile = INVALID_HANDLE_VALUE;
#else
fd = other.fd;
other.fd = -1;
#endif
}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
close();
#ifdef _WIN32
hFile = other.hFile;
other.hFile = INVALID_HANDLE_VALUE;
#else
fd = other.fd;
other.fd = -1;
#endif
}
return *this;
}
~FileHandle() { close(); }
std::size_t write(const std::vector <char>& buf) {
#ifdef _WIN32
DWORD written = 0;
if (!WriteFile(hFile, buf.data(), static_cast <DWORD>(buf.size()), &written, nullptr))
throw std::system_error(GetLastError(), std::system_category(), "Write failed");
return written;
#else
ssize_t res = ::write(fd, buf.data(), buf.size());
if (res < 0)
throw std::system_error(errno, std::generic_category(), "Write failed");
return res;
#endif
}
std::size_t read(std::vector <char>& buf, std::size_t count) {
#ifdef _WIN32
DWORD read = 0;
if (!ReadFile(hFile, buf.data(), static_cast <DWORD>(count), &read, nullptr))
throw std::system_error(GetLastError(), std::system_category(), "Read failed");
return read;
#else
ssize_t res = ::read(fd, buf.data(), count);
if (res < 0)
throw std::system_error(errno, std::generic_category(), "Read failed");
return res;
#endif
}
private:
void close() {
#ifdef _WIN32
if (hFile != INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
#else
if (fd >= 0) {
::close(fd);
fd = -1;
}
#endif
}
#ifdef _WIN32
HANDLE hFile = INVALID_HANDLE_VALUE;
#else
int fd = -1;
#endif
};
4. 使用示例
#include "file_handle.hpp"
#include <iostream>
#include <vector>
int main() {
try {
// 写入文件
FileHandle writer("test.txt", FileHandle::Append);
std::string data = "Hello, RAII!\n";
writer.write(std::vector <char>(data.begin(), data.end()));
// 读取文件
FileHandle reader("test.txt", FileHandle::Read);
std::vector <char> buffer(1024);
std::size_t n = reader.read(buffer, buffer.size());
std::string content(buffer.begin(), buffer.begin() + n);
std::cout << "File content:\n" << content << std::endl;
} catch (const std::system_error& e) {
std::cerr << "系统错误: " << e.what() << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
5. 优点与扩展
- 安全:资源在对象生命周期结束时自动释放,避免泄漏。
- 可维护:将平台差异隐藏在实现细节中,调用者无需关心。
- 可扩展:可以在此基础上添加 seek、文件大小查询、锁定等功能。
6. 结语
RAII 通过让资源的拥有者成为 C++ 对象的生命周期来简化错误处理与资源释放。文件操作是最直观的示例之一,掌握好后可以在更大范围内(如网络套接字、内存映射文件等)运用相同思路,编写出更可靠、更易维护的系统代码。