【问题标题】:Modern C++ wrapper around C Dirent.h围绕 C Dirent.h 的现代 C++ 包装器
【发布时间】:2015-08-12 21:12:04
【问题描述】:

我正在尝试围绕 c 头文件 dirent.h 编写一个现代 c++ 包装器。

要在 C 中读取目录的内容,可以编写如下内容:

int listdir(const char *path) {
  struct dirent *entry;
  DIR *dp;

  dp = opendir(path);
  if (dp == NULL) {
    perror("opendir");
    return -1;
  }

  while((entry = readdir(dp)))
    puts(entry->d_name);

  closedir(dp);
  return 0;
}

将其转换为现代 c++,我有以下内容(其中 m_dir 和 m_dirent 是 std::unique_ptr,m_files 是 std::vector<string>

filesystem::Directory::Directory(std::string dir) : m_dir(opendir(dir.c_str()), closedir),
                                                  m_dirent(new struct dirent())
{
    //If the directory can not be read, throw an error.
    if (!m_dir) {
        sdl2::SDLFileSystemRead_Failure ex;
        throw ex;
    }

    while (&(*m_dirent = *readdir(&*m_dir)))
    {

        m_files.emplace_back(std::string(m_dirent->d_name));

    }

}

这只有一半有效。当我写它时,我没有注意到我只是在检查表达式*m_dirent = *readdir(&*m_dir) 的地址是否存在(当然存在!)。 根据The Single UNIX ® Specification, Version 2,如果 readdir(DIR*) 已读取最后一个文件并且没有更多文件要读取,则返回一个空指针。但是,我不确定如何在不调用m_dirent 上的.reset() 的情况下将dirent 指针设置为dir 指针正在读取的任何内容。但是,这样做只会导致读取垃圾数据,因为我假设文件指针在dirent 被破坏时丢失。

如何转换

  while((entry = readdir(dp)))
    puts(entry->d_name);

进入现代 C++?

【问题讨论】:

  • 一般来说,潜在的问题是您正试图进行从 C 到 C++ 的逐行转换。这不是将基于非 OO、C 的 API 转换为 C++ 接口的最佳方式。您必须查看整体设计,并对其应用适当的模式。举个例子,看看boost是如何实现文件系统API的。
  • 查看已被技术报告 2 接受的 Boost.FileSystem (boost.org/doc/libs/release/libs/filesystem)。
  • 如果您可以访问 Koenig 的 Ruminations on C++,他的其中一章中有一个例子。但是,这可能不算作现代 C++,因为它比标准早了一年左右,所以它没有从那时起构建的所有经验。
  • @JonathanLeffler:我不知道 Koenig 在目录中有什么东西。你还记得它是如何被曝光的吗?特别是,Koenig 是否将目录公开为迭代器?
  • @DietmarKühl:我需要再次查看代码。 IIRC,有关于使其成为迭代器的讨论,但没有实际这样做的代码。

标签: c++ pointers c++11


【解决方案1】:

我不确定这个计数是否像我在上个千年在 UseNet 中发布的那样现代here 是一个改进的版本)。它稍有发展,成为 1998 年用于播种 Boost 的第一批组件之一。由于其他人的工作,它在 Boost 得到了进一步发展,最终变成了 file system library,它构成了 File System TS 的基础.

然而,这一切都始于一个简单的想法:如何很好地公开opendir()readdir()closedir()?比较明显的答案是:使用迭代器!这是一个简单的版本和演示:

#include <iostream>
#include <iterator>
#include <algorithm>
#include <string>
#include <stdexcept>
#include <memory>
#include <dirent.h>

class dir_it
    : public std::iterator<std::input_iterator_tag, std::string>
{
    std::shared_ptr<DIR> dir;
    std::string          current;
    void advance() {
        dirent  entry;
        dirent* result;
        if (!readdir_r(dir.get(), &entry, &result) && result) {
            this->current = result->d_name;
        }
        else {
            this->dir.reset();
        }
    }
public:
    dir_it(std::string const& path)
        : dir(opendir(path.c_str()), [](DIR* dir){ dir && closedir(dir); }) {
        if (!dir) {
            throw std::runtime_error("failed to open directory '" + path + "'");
        }
        this->advance();
    }
    dir_it(): dir() {}

    std::string const& operator*() const { return this->current; }
    dir_it&            operator++() { this->advance(); return *this; }
    dir_it             operator++(int) {
        dir_it rc(*this);
        this->operator++();
        return rc;
    }
    bool operator==(dir_it const& other) const {
        return bool(this->dir) == bool(other.dir);
    }
    bool operator!=(dir_it const& other) const {
        return !(*this == other);
    }
};

int main() {
    std::copy(dir_it("."), dir_it(), std::ostream_iterator<std::string>(std::cout, "\n"));
}

当然,Boost 中的文件系统库和文件系统 TS 中的文件系统库比这种有点幼稚的实现功能要强大得多。如果您的实现附带 TS 的实现,我会使用它。如果不是,您可能需要考虑 Boost 的实现。

【讨论】:

    【解决方案2】:

    逗号运算符来救援!

    while(entry.reset(readdir(m_dir.get())), entry)
        puts(entry->d_name);
    

    尽管正如 cmets 所说,这对于文件系统来说并不是一个出色的 API。例如,透明地适应底层目录的迭代器会很酷。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-15
      • 1970-01-01
      • 2015-04-13
      • 1970-01-01
      • 1970-01-01
      • 2013-03-07
      • 1970-01-01
      • 2013-08-14
      相关资源
      最近更新 更多