• 首页
  • 加入
  • RSS
  • QDir 和 std::filesystem 的简单对比

    Friday, March 4, 2022
    deepin 社区官方博客 #deepin-community:deepin.org


    作为一名使用 Qt 的开发人员,Qt 为我提供了大量好用的基础设施,例如广受好评的 QString、QNetwork之类的,这是 Qt 平台为我提供的帮助,我只需要在这个平台上开发就足够了。

    同样作为一名 C++ 开发人员,C++ 标准库也是我需要用的基础设施,但是标准库提供的功能就不如 Qt 了,最令人诟病的就是 C++ 的 std::string,业内充斥着对 std::string 的不屑与谩骂。

    但是这一情况将会在 C++ 20 标准后改善,这里不具体展开,将来我会准备写一份 C++ 20 的功能介绍。

    今天在开发项目的时候,为了节省资源,我仅使用标准库完成开发,缺少了 Qt 平台为我提供的有利帮助,项目开发的难度瞬间增加,在此期间,我发现了 C++ 的目录操作似乎能比的上 Qt 提供的 QDir封装,我打算在本篇文章中介绍一下这两者的不同。

    QDir

    Qt 的基本思路是继承大于组合,所以 Qt 为我们提供的都是各种继承的类,在 Qt 中,我们使用 QDir 类进行目录操作。

    QDir 类使用相对或绝对文件路径来指向一个文件或目录。如果总是使用 “/” 作为目录分隔符,Qt 将会把你的路径转化为符合底层的操作系统的。

    QDir 类由于不涉及 IO 的具体操作,所以没有继承自 QObject 或者其他 QIO 的类。

    QDir 提供了非常多的方法,可以方便的获取目录的名称、绝对路径、相对路径、设置目录过滤器等。

    一个基本的用法如下:

    QDir dir("/tmp");
    if (!dir.exists()) {
        return
    }
    
    for (const QFileInfo& item : dir.entryInfoList()) {
        if (item.isDir()) {
            // ...
          }
    }
    
    dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
    dir.setSorting(QDir::Size | QDir::Reversed);
    
    const QFileInfoList& list = dir.entryInfoList();
    for (const QFileInfo& item : list) {
        // ...
    }
    

    在上面的示例代码中可以看到,QDir 整体是非常符合面向对象的,我们使用 QDir 对象,对目录执行各种操作,涉及到具体的文件(目录是特殊的文件),我们可以使用 QFileInfo 类获取具体文件的信息。

    除了以上演示涉及到的方法,QDir 还有很多其他方法,使用 count() 方法统计目录内文件和目录的数量,使用 remove() 方法删除指定的目录,这里就不一一列举了。

    QDir 支持设置多种过滤,过滤(Filter) 和 排序(Sorting) 都会影响到 entryInfoList 方法返回的内容。

    QDir 接受的过滤器枚举:

    枚举枚举值描述
    QDir::Dirs0x001列出与过滤器匹配的目录。
    QDir::AllDirs0x400列出所有目录;即不要将过滤器应用于目录名称。
    QDir::Files0x002列出文件。
    QDir::Drives0x004列出磁盘驱动器(在Unix下忽略)。
    QDir::NoSymLinks0x008不要列出符号链接(不支持符号链接的操作系统忽略)。
    QDir::NoDotAndDotDotNoDotNoDotDot
    QDir::NoDot0x2000不要列出特殊条目“.”。
    QDir::NoDotDot0x4000不要列出特殊条目“..”。
    QDir::AllEntriesDirsFiles
    QDir::Readable0x010列出应用程序具有读取访问权限的文件。可读值需要与Dirs或Files合并。
    QDir::Writable0x020列出应用程序具有写入访问权限的文件。可写值需要与Dirs或Files合并。
    QDir::Executable0x040列出应用程序具有执行访问权限的文件。可执行值需要与Dirs或Files合并。
    QDir::Modified0x080仅列出已修改的文件(在Unix上忽略)。
    QDir::Hidden0x100列出隐藏的文件(在Unix上,以“.”开头的文件)。
    QDir::System0x200列出系统文件(包括Unix、FIFO、套接字和设备文件;在Windows上,包括.lnk文件)
    QDir::CaseSensitive0x800过滤器应该区分大小写。

    QDir 接受的排序枚举:

    枚举枚举值描述
    QDir::Name0x00按名称排序。
    QDir::Time0x01按时间(修改时间)排序。
    QDir::Size0x02按文件大小排序。
    QDir::Type0x80按文件类型(扩展名)排序。
    QDir::Unsorted0x03不要排序。
    QDir::NoSort-1默认情况下未排序。
    QDir::DirsFirst0x04先放目录,然后放文件。
    QDir::DirsLast0x20先放文件,然后放目录。
    QDir::Reversed0x08颠倒排序顺序。
    QDir::IgnoreCase0x10不区分大小写的排序。
    QDir::LocaleAware0x40使用当前区域设置对项目进行适当排序。

    std::filesystem

    上面介绍了 Qt 的设计风格,而标准库的设计风格是组合大于继承,标准库提供各种非常具体的类或者函数,将一个系列的操作拆分为各个子项,最终完成任务。

    这里使用一个小例子来说明一下:

    std::filesystem::path tmp{"/tmp"};
    if (!std::filesystem::exists(tmp)) {
        std::cout << "目录不存在" << std::endl;
        return;
    }
    
    const bool result = std::filesystem::create_directories(tmp);
    if (!result) {
        std::cout << "目录创建失败,也许是已经存在。" << std::endl;
    }
    
    for (auto const& dir_entry : std::filesystem::directory_iterator(tmp)) {
        if (dir_entry.is_directory()) {
            // ...
        }
    }
    
    if (std::filesystem::is_directory(tmp)) {
        // ...
    }
    

    可以看到,C++ 标准库使用多个类共同完成了对目录的检查和遍历,这种基于组合的方式可以带来更多的灵活性,如果需要对某个部分进行修改,只需要继承特定类就可以完成,如果是 Qt 的 QDir,则不是很轻松。

    在使用标准库的时候需要注意的是,标准库通常不会约束使用者,使用当前的例子举例,QDir 提供了过滤器枚举,可以帮助开发者简单的实现文件过滤功能,但是 std::filesystem 则不提供这种接口,标准库提供了机制,但是不提供策略,开发者需要使用标准库提供的各种接口,组合 出自己的业务,所以如果想要使用标准库的时候也能实现过滤和排序,就只能自己提供相应的操作。

    std::filesystem::path tmp{ "/tmp" };
    std::vector<std::filesystem::directory_entry> list;
    std::vector<std::string> filter{ ".jpg", ".txt" };
    std::filesystem::directory_iterator iter{ tmp };
    std::copy_if(iter.begin(),
                 iter.end(),
                 std::back_inserter(list),
                 [filter](std::filesystem::directory_entry entry) {
                    return !entry.is_directory() && 
                           std::find_if(filter.begin(),
                                        filter.end(),
                                        [entry](std::string s) {
                                          return entry.path().extension() == s;
                                        }
                           );
                  }
    );
    

    可以看到,代码变得异常丑陋(逃

    标准库提供了一些比较方便的函数,例如 std::copy_if、 std::back_inserter 和 std::find_if 等,还有一个较为常用的 std::transform。标准库提供了迭代器抽象,这样我们可以使用迭代器对象和迭代器算法,方便的进行各种遍历、复制和转换。

    从上面的例子可以看出,Qt 确实为开发者提供了很好的帮助,这是 Qt 作为一个平台力所能及的工作,当然,即使我们使用 Qt,也还是可以写出上面一样的代码。

    总结

    以上就是简单的 QDir 和 std::filesystem 的不同,综合来看,Qt 库和标准库其实各有优缺,他们的目标和面向的开发者是不同的,Qt 最近一直在尝试使用标准库的内容来代替自己的一部分组件,最新的Qt6 就已经升级到了 C++ 17 标准,将 qSort 宏改为了使用 std::sort,也许在不久的将来,我们再也不用为使用 Qt 还是标准库而争论或者站队。

    引用资料 std::filesystem https://en.cppreference.com/w/cpp/filesystem QDir https://doc.qt.io/qt-5/qdir.html

    原文链接:https://blog.justforlxz.com/2022/03/04/qdir-stdfilesystem/