在现代C++中,std::filesystem(在C++17中正式加入)为文件系统操作提供了一套统一、跨平台的接口。本文将演示如何利用它实现一个简易的文件复制工具,并讨论一些常见的错误处理和性能优化技巧。
1. 环境准备
- 编译器:gcc 8+ / clang 9+ / MSVC 2017+,均支持
std::filesystem。 - 标准选项:
-std=c++17(或更高)。
2. 基本思路
复制文件的核心步骤如下:
- 打开源文件(
std::ifstream)和目标文件(std::ofstream)。 - 以二进制模式读写,缓冲区可以是固定大小(如 8KB)。
- 逐块复制,直到源文件结束。
- 处理异常:文件不存在、权限不足、磁盘空间不足等。
使用std::filesystem可简化路径检查、文件属性获取和错误报告。
3. 代码实现
#include <filesystem>
#include <fstream>
#include <iostream>
#include <vector>
namespace fs = std::filesystem;
// 把单个文件从src复制到dst
bool copy_file(const fs::path& src, const fs::path& dst, std::error_code& ec) {
// 确认源文件存在且可读
if (!fs::exists(src, ec) || !fs::is_regular_file(src, ec)) {
ec = std::make_error_code(std::errc::no_such_file_or_directory);
return false;
}
// 创建目标目录(如果不存在)
fs::path dst_dir = dst.parent_path();
if (!dst_dir.empty() && !fs::exists(dst_dir, ec)) {
fs::create_directories(dst_dir, ec);
if (ec) return false;
}
std::ifstream in(src, std::ios::binary);
std::ofstream out(dst, std::ios::binary);
if (!in) { ec = std::make_error_code(std::errc::io_error); return false; }
if (!out) { ec = std::make_error_code(std::errc::io_error); return false; }
const std::size_t buffer_size = 8192; // 8KB
std::vector <char> buffer(buffer_size);
while (in) {
in.read(buffer.data(), buffer_size);
std::streamsize bytes = in.gcount();
if (bytes > 0) out.write(buffer.data(), bytes);
if (!out) { ec = std::make_error_code(std::errc::io_error); return false; }
}
return true;
}
// 复制目录下的所有文件(递归)
bool copy_directory(const fs::path& src, const fs::path& dst, std::error_code& ec) {
if (!fs::exists(src, ec) || !fs::is_directory(src, ec)) {
ec = std::make_error_code(std::errc::not_a_directory);
return false;
}
for (auto& entry : fs::recursive_directory_iterator(src, ec)) {
if (ec) return false;
const fs::path& src_path = entry.path();
fs::path relative = fs::relative(src_path, src, ec);
if (ec) return false;
fs::path dst_path = dst / relative;
if (fs::is_directory(src_path, ec)) {
fs::create_directory(dst_path, ec);
if (ec) return false;
} else if (fs::is_regular_file(src_path, ec)) {
if (!copy_file(src_path, dst_path, ec)) return false;
}
}
return true;
}
int main() {
std::error_code ec;
fs::path src = "src_folder";
fs::path dst = "dst_folder";
if (copy_directory(src, dst, ec)) {
std::cout << "复制完成!\n";
} else {
std::cerr << "复制失败: " << ec.message() << "\n";
}
return 0;
}
4. 关键细节说明
-
异常 vs. error_code
std::filesystem默认使用异常机制,fs::exists、fs::create_directories等函数会抛出std::filesystem::filesystem_error。若想避免异常,传入std::error_code &ec参数即可。本文统一使用error_code,更易于错误聚合和日志记录。 -
缓冲区大小
8KB 是一个折衷的大小;对磁盘 I/O 性能影响不大;若在网络文件系统上,可根据带宽调整。 -
权限与所有权
默认复制后,目标文件拥有调用进程的用户权限。若需要保留原文件的权限和时间戳,可在复制完成后使用fs::permissions、fs::last_write_time等函数同步属性。 -
符号链接与特殊文件
fs::recursive_directory_iterator会遍历符号链接。默认情况下,is_directory会返回链接指向的目录。若想复制链接本身而不是目标,可使用fs::directory_options::skip_permission_denied或自行处理is_symlink。 -
性能优化
- 内存映射(
mmap)适用于大文件复制,但与标准库兼容性差。 - 多线程:将目录拆分为多线程任务,可显著提升磁盘 I/O 并行度,但需注意同步和锁的开销。
- 内存映射(
5. 常见错误处理
| 场景 | 典型错误码 | 解决办法 |
|---|---|---|
| 源文件不存在 | ENOENT |
检查路径拼写,使用绝对路径 |
| 目标目录不可写 | EACCES |
确认用户权限,使用 sudo 或修改权限 |
| 磁盘空间不足 | ENOSPC |
清理磁盘,或限制复制范围 |
| 链接循环 | ELOOP |
设置 directory_options::follow_directory_symlink 或 skip |
6. 结语
利用C++17的std::filesystem可以极大地简化跨平台文件系统操作,并保持代码简洁可读。通过上述示例,你可以快速搭建自己的文件复制工具,并根据需求进一步扩展功能,例如支持增量同步、文件压缩或网络传输。希望这篇文章对你在实际项目中的文件操作有所帮助。