replace_filename() 仅替换整个文件名部分,不保留扩展名;正确做法是用 new_stem + p.extension().string() 构造新文件名,再调用 std::filesystem::rename() 才真正重命名。

用 std::filesystem::path::replace_filename() 重命名文件但保留扩展名
直接结论:replace_filename() 不会自动保留原始扩展名,它只是替换整个文件名(含扩展名)部分。如果你传入一个不含点的字符串(比如 "newname"),结果路径会变成 "dir/newname"——扩展名直接丢了。
正确做法是先提取原扩展名,再拼回去。常见错误是直接写 p.replace_filename("newname"),导致 .txt、.log 全部消失。
-
p.replace_filename("newname" + p.extension().string())是最简方案 - 注意
p.extension()返回的是包括点的字符串(如".cpp"),所以不用手动加"." - 如果原文件无扩展名(
p.extension().empty()为true),拼接后也不会多出点,安全
为什么不能只用 replace_filename("newname")?
因为 std::filesystem::path::filename() 返回的是“最后一级名称”,而 replace_filename() 替换的就是这一整块。例如:
std::filesystem::path p = "/home/user/report_v1.pdf"; // p.filename() == "report_v1.pdf" // p.stem() == "report_v1" // p.extension() == ".pdf"
调用 p.replace_filename("final") 后,路径变成 /home/user/final,.pdf 彻底丢失。
立即学习“C++免费学习笔记(深入)”;
- 误以为
replace_filename()类似于“改名字不改后缀”的 GUI 操作——实际它没有语义理解,纯字符串层级替换 -
stem()和extension()是分离设计,必须显式组合 - Windows 下大小写不敏感,但
extension()仍返回原始大小写(如".TXT"),需留意一致性
完整重命名操作:检查存在性 + 原子替换
仅改路径对象没用,得调用 std::filesystem::rename() 才真正生效。而且必须处理目标路径是否已存在——否则抛 std::filesystem::filesystem_error。
- 先构造新路径:
auto new_p = p.parent_path() / (new_stem + p.extension().string()); - 用
std::filesystem::exists(new_p)判断是否冲突(尤其在日志轮转等场景下很关键) - 调用
std::filesystem::rename(p, new_p);该操作在同文件系统下是原子的,不会出现中间态 - 若跨分区失败,会抛异常,需捕获
std::filesystem::filesystem_error并检查.code().value()
性能与兼容性注意点
replace_filename() 本身是纯内存操作,零开销;真正耗时的是后续的 rename() 系统调用。
- C++17 起标准支持,MSVC 19.14+、GCC 8.1+、Clang 7.0+ 均可用;旧编译器需自行实现或用 Boost.Filesystem
- Linux/macOS 下 rename 同分区极快;Windows 上 NTFS 同卷也快,但 FAT32 或网络路径可能降级为复制+删除
- 不要对正在被其他进程独占打开的文件重命名(尤其是 Windows),会失败且无回退机制
最易被忽略的是:扩展名来自原始路径对象,不是文件实际内容。如果文件被重命名过多次,p.extension() 仍取决于最后一次构造时的字符串,和磁盘上内容无关。



















