c++oding="utf-8" ?>
std::filesystem::copy_file 默认非原子,需配合临时文件与rename实现原子性;它不自动处理跨文件系统、权限继承、符号链接解引用等问题,且性能低于系统级拷贝工具。

copy_file 默认不是原子的,必须显式指定 copy_options::skip_existing 或 copy_options::overwrite_existing
很多人以为 std::filesystem::copy_file 天然原子——其实它只是“单次调用完成复制”,但底层仍分三步:读源文件、写目标文件、设权限/时间戳。中间出错(如磁盘满、权限拒绝)会导致目标文件残留半成品。
真正能逼近原子语义的做法,是让目标路径始终指向一个完整副本,靠重命名切换。而 copy_file 本身不提供“先写临时再 rename”逻辑,得自己组合:
• 必须用 copy_options::overwrite_existing(否则遇到已存在文件直接抛 std::filesystem::filesystem_error)
• 目标路径不能是正在被其他进程读取的活跃文件(Windows 下会失败)
• 不支持跨文件系统移动;若源和目标在不同挂载点,copy_file 内部会退化为逐块拷贝,失败时无回滚
要实现真正原子备份,必须配合 std::filesystem::rename 和临时文件
核心思路是:复制到一个带随机后缀的临时路径(如 "backup.bak.tmp"),复制成功后再用 rename 原子替换原备份文件。这是 POSIX 和 Windows(NTFS)都保证原子的操作。
常见错误:
• 临时文件和目标文件放在不同目录(导致 rename 失败,报 EXDEV)
• 没检查 rename 返回值,误以为替换一定成功
• 临时文件名没加进程/时间戳,多进程并发时互相覆盖
实操建议:
• 临时路径和目标路径用同一父目录,例如:std::filesystem::path tmp = backup_path.parent_path() / ("." + backup_path.filename().string() + ".tmp")
• 复制前先 remove 旧临时文件(避免残留)
• copy_file 后立即 rename,中间不做任何可能崩溃的操作
Windows 下要注意 copy_options::skip_symlinks 和权限继承问题
Windows 的 NTFS 权限模型比 Unix 复杂,copy_file 默认不复制 ACL,只复制基本读写属性。如果你的备份需保留 DACL(比如服务账户依赖特定权限),必须手动调用 SetNamedSecurityInfo(Windows API),std::filesystem 不管这事。
另一个坑:
• 若源文件是符号链接(symlink),默认行为是复制链接本身(而非目标内容),容易误判为“空文件”
• 需显式传 copy_options::copy_symlinks 才解引用复制目标内容
• 但 Windows 上 symlink 创建需要管理员权限或开发者模式开启,普通用户环境可能根本建不出 symlink,所以实际中更常见的是 junction 或 hard link,而 filesystem 对 junction 不识别,会当普通目录处理
性能敏感场景下,copy_file 比 system("cp") 慢不少
实测在 Linux 上,copy_file 平均比 cp --reflink=auto 慢 2–5 倍,尤其对大文件(>1GB)。因为标准库实现通常用 read()/write() 循环,没用 sendfile 或 copy_file_range 系统调用。
如果你的备份场景强调速度且文件系统支持(XFS/Btrfs/ext4 with reflink),不如直接调用:
• system("cp --reflink=always -f source dest")(Linux)
• 或封装 CopyFileEx(Windows),它支持 COPY_FILE_NO_BUFFERING 和 COPY_FILE_RESTARTABLE
注意:
• copy_file 在 C++17 中不承诺使用零拷贝优化,各 STL 实现差异大(libstdc++ vs libc++)
• 即使开了 -O3,编译器也无法帮你在运行时选最优 syscall
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











