跟我学c++中级篇—c++17的filesystem主要功能
一、文件处理
其实开发者在实际的开发中都会发现,对文件IO的操作,是相当麻烦的。如果只是在单一的平台上开发,可能还稍微好一些,只需处理本平台相关的操作接口就可以了。
但是又发现,几乎所有的对文件或文件夹的操作都存在着一个共性,那就是相关的接口以及数据结构都相当的复杂。大家当然可以理解为了兼容内核的底层库必须要把相关的细节暴露出来的设计理念。但对于更多的应用者来说,其实有很多的细节意义并不大。
比如在前面Linux的数据结构“struct statvfs”,可能很多开发者见而生畏。
二、std::filesystem
随着技术的发展,跨平台的需求越来越多,而C++的标准也在不断在迭代。在C++17的标准中,提供了一个filesystem库,它解决了原有接口不统一、跨平台不方便不兼容以及数据结构复杂的痛点。特别是通过分解相关功能,利用不同的模块来处理文件相关的各类属性,让上层应用者不必关心过多的文件操作细节。同时,它还支持Unicode、软硬链接及权限管理等操作,为关心细节操作的开发者提供了更方便的接口操作方法。
有过Boost开发经验的,很可能就会想到Boost中的Filesystem,没错。不然,怎么会把Boost称为标准库的预备库呢?
filesystem的主要相关内容如下:
可以发现,其包含的相关的内容还是相当多的。不过对于普通开发者,最方便的就是解决了文件路径操作的不方便,不用自己再手动的来回来去的组装和拆解文件路径了。
三、应用
下面对filesystem的主要用法进行分析和说明:
文件路径操作
这可以说是开发者的福音,其引入的filesystem::path提供了路径字符串的拆分、组合及相关的解析等。最典型的就是直接从全路径中获取文件名等:#include<filesystem>#include<iostream>namespace fs=std::filesystem;intmain(){std::cout<<fs::path("/foo/bar.txt").filename()<<'\n';std::cout<<fs::path("/foo").replace_filename("bar")<<'\n';fs::path p;std::cout<<std::boolalpha<<(p="foo/bar").remove_filename()<<'\t'<<p.has_filename()<<'\n';std::cout<<fs::path("/foo/bar.txt").extension()<<'\n';for(constfs::path p:{"/foo/bar.txt","/foo/.bar","foo.bar.baz.tar"})std::cout<<"path: "<<p<<", stem: "<<p.stem()<<'\n';std::cout<<'\n';for(fs::path p="foo.bar.baz.tar";!p.extension().empty();p=p.stem())std::cout<<"path: "<<p<<", extension: "<<p.extension()<<'\n';}文件目录操作
一般来说,常见的目录操作不外乎创建、拷贝、重命名、删除目录、遍历当前或递归遍历当前目录等等,看代码:#include<cassert>#include<cstdlib>#include<filesystem>intmain(){std::filesystem::current_path(std::filesystem::temp_directory_path());// Basic usagestd::filesystem::create_directories("sandbox/1/2/a");std::filesystem::create_directory("sandbox/1/2/b");// Directory already exists (false returned, no error)assert(!std::filesystem::create_directory("sandbox/1/2/b"));std::cout<<"directory_iterator:\n";// directory_iterator can be iterated using a range-for loopfor(autoconst&dir_entry:std::filesystem::directory_iterator{sandbox})std::cout<<dir_entry.path()<<'\n';std::cout<<"\ndirectory_iterator as a range:\n";// directory_iterator behaves as a range in other ways, toostd::ranges::for_each(std::filesystem::directory_iterator{sandbox},[](constauto&dir_entry){std::cout<<dir_entry<<'\n';});std::cout<<"\nrecursive_directory_iterator:\n";for(autoconst&dir_entry:std::filesystem::recursive_directory_iterator{sandbox})std::cout<<dir_entry<<'\n';fs::create_directories("sandbox/dir/subdir");std::ofstream("sandbox/file1.txt").put('a');fs::copy("sandbox/file1.txt","sandbox/file2.txt");// copy filefs::copy("sandbox/dir","sandbox/dir2");// copy directory (non-recursive)constautocopyOptions=fs::copy_options::update_existing|fs::copy_options::recursive|fs::copy_options::directories_only;fs::copy("sandbox","sandbox_copy",copyOptions);static_cast<void>(std::system("tree"));// Permissions copying usagestd::filesystem::permissions("sandbox/1/2/b",std::filesystem::perms::others_all,std::filesystem::perm_options::remove);std::filesystem::create_directory("sandbox/1/2/c","sandbox/1/2/b");std::system("ls -l sandbox/1/2");std::system("tree sandbox");std::filesystem::remove_all("sandbox");}通过上述操作,可以进行文件目录的过滤、修改和文件处理等功能操作。
属性操作
文件属性的操作也是非常常见的,包括前面提到的Space空间处理,其还有很多如类型、时间、状态及大小等等相关,可参看上面的那个图中的相关属性。namespace fs=std::filesystem;voiddemo_status(constfs::path&p,fs::file_status s){std::cout<<p;switch(s.type()){casefs::file_type::none:std::cout<<" has `not-evaluated-yet` type";break;casefs::file_type::not_found:std::cout<<" does not exist";break;casefs::file_type::regular:std::cout<<" is a regular file";break;casefs::file_type::directory:std::cout<<" is a directory";break;casefs::file_type::symlink:std::cout<<" is a symlink";break;default:std::cout<<" has `implementation-defined` type";break;}std::cout<<'\n';}voiddemo_exists(constfs::path&p,fs::file_status s=fs::file_status{}){std::cout<<p;if(fs::status_known(s)?fs::exists(s):fs::exists(p))std::cout<<" exists\n";elsestd::cout<<" does not exist\n";}intmain(){// create files of different kindsfs::create_directory("sandbox");fs::create_directory("sandbox/dir");std::ofstream{"sandbox/file"};// create regular filefs::create_symlink("file","sandbox/symlink");// demo different status accessorsfor(autoit{fs::directory_iterator("sandbox")};it!=fs::directory_iterator();++it)demo_status(*it,it->symlink_status());// use cached status from directory entrydemo_status("/dev/null",fs::status("/dev/null"));// direct calls to statusdemo_status("/dev/sda",fs::status("/dev/sda"));demo_status("sandbox/no",fs::status("/sandbox/no"));demo_exists(sandbox);// cleanup (prefer std::unique_ptr-based custom deleters)close(fd);fs::remove_all("sandbox");}更多的属性操作请参看具体的cppreference中的相关文档说明。
符号链接操作
处理符号链接是一个比较突出的问题,这个在Linux平台上可能更容易遇到。namespace fs=std::filesystem;intmain(){fs::create_directories("sandbox/subdir");fs::create_symlink("target","sandbox/sym1");fs::create_directory_symlink("subdir","sandbox/sym2");for(autoit=fs::directory_iterator("sandbox");it!=fs::directory_iterator();++it)if(is_symlink(it->symlink_status()))std::cout<<*it<<"->"<<read_symlink(*it)<<'\n';assert(std::filesystem::equivalent("sandbox/sym2","sandbox/subdir"));std::ofstream("sandbox/a").put('a');// create regular filefs::create_hard_link("sandbox/a","sandbox/b");fs::remove("sandbox/a");// read from the original file via surviving hard linkcharc=std::ifstream("sandbox/b").get();std::cout<<c<<'\n';fs::remove_all("sandbox");for(fs::path p:{"/usr/bin/gcc","/bin/cat","/bin/mouse"}){std::cout<<p;fs::exists(p)?fs::is_symlink(p)?std::cout<<" -> "<<fs::read_symlink(p)<<'\n':std::cout<<" exists but it is not a symlink\n":std::cout<<" does not exist\n";}}错误处理
这个就是一种安全机制了,也可以算作异常的处理:#include<filesystem>#include<iostream>#include<system_error>intmain(){conststd::filesystem::path from{"/none1/a"},to{"/none2/b"};try{std::filesystem::copy_file(from,to);// throws: files do not exist}catch(std::filesystem::filesystem_errorconst&ex){std::cout<<"what(): "<<ex.what()<<'\n'<<"path1(): "<<ex.path1()<<'\n'<<"path2(): "<<ex.path2()<<'\n'<<"code().value(): "<<ex.code().value()<<'\n'<<"code().message(): "<<ex.code().message()<<'\n'<<"code().category(): "<<ex.code().category().name()<<'\n';}// All functions have non-throwing equivalentsstd::error_code ec;std::filesystem::copy_file(from,to,ec);// does not throwstd::cout<<"\nNon-throwing form sets error_code: "<<ec.message()<<'\n';}辅助功能
这个也比较多,比如绝对和规范路径的处理(absolute,canonical接口),临时路径等等:namespace fs=std::filesystem;voidshow(std::filesystem::path x,std::filesystem::path y){std::cout<<"x:\t\t "<<x<<"\ny:\t\t "<<y<<'\n'<<"relative(x, y): "<<std::filesystem::relative(x,y)<<'\n'<<"proximate(x, y): "<<std::filesystem::proximate(x,y)<<"\n\n";}intmain(){std::filesystem::path p="foo.c";std::cout<<"Current path is "<<std::filesystem::current_path()<<'\n';std::cout<<"Absolute path for "<<p<<" is "<<fs::absolute(p)<<'\n';show("/a/b/c","/a/b");show("/a/c","/a/b");show("c","/a/b");show("/a/b","c");}
说明:以上代码均来自cppreference
四、常见问题
虽然获得了使用filesystem的便利性,但也不得不考虑在应用时的一些问题的异常行为。主要包括:
- 权限处理和使用
filesystem提供的perms权限处理有点简陋,如果要在Linux上进行复杂的权限操作则无法满足,仍然需要调用底层的接口。另外,在使用其时也要注意有无权限操作相关文件的权限问题,否则可能导致操作失败 - 跨平台的路径格式
在不同的平台路径分隔符可能有所不同,如Windows平台使用“\”而Linux使用“/”,所以在直接操作时要注意。另外Windows平台和Linux平台的分区格式、路径长度(Windows限制为260字符,Win10后支持长路径)也不相同,都需要引起注意。一般建议是始终使用“/”,让path自行转换相关平台应用 - 符号链接的问题
在操作符号链接时,问题就比较多了,首先要确认到底想操作链接还是源目标,不要搞错。另外,还要处理空链接(源不存在仍然创建成功)创建问题;硬链接创建的条件(无法创建目录的硬链接等);循环链接。另外要弄清楚read_symlink与canonical的不同,前者只取单纯指向的链接源(不管源是否再指向其它),而后者则会找到最后的真实文件 - 文件名称的大小写问题
这个一定要高度注意,很多从Windows转Linux上的吃过大亏。前者是不区分大小写的,而后者严格区分。特别是O等几个不太明显的字符,一定要小心 - 性能问题
在实际的应用中,可能会对一个目录进行递归遍历或者拷贝一个很大的文件夹,这都可能导致性能的下降。需要开发者自行处理类似的情况
更多的具体的细节问题,可以在使用filesystem的接口时,查看相关的说明。还是写得相当清楚的。
五、总结
俗话说的好,大路不平旁人铲。自己不产,别人产。从现在AI辅助编程工具的发展看,再过几年,可能就不是单纯的标准向着自然语言转了,而是直接由AI创建自然语言接口,然后纯口语编程了。
