【问题标题】:Path to file or stream?文件或流的路径?
【发布时间】:2016-12-02 03:55:47
【问题描述】:

我正在与一位同事讨论,我认为这是一个很好的问题,可以在这里提出。

在设计和 API 时,您的函数什么时候应该接受文件路径,什么时候应该接受流?有什么指导方针吗?

void do_something(const std::filesystem::path &file_path);
void do_something(std::istream &stream);

路径:

  • 被调用者负责检查文件是否存在且可访问。
  • 难以进行单元测试。您必须在磁盘上创建/保存一个文件才能对其进行测试。

流:

  • 调用者负责检查文件是否存在并且可以访问。更多重复的样板代码。
  • 单元测试更简单,您只需传递一个流对象即可

我想可以在库中添加一个函数来“帮助”打开文件,诸如此类:

std::ifstream open_input(const std::filesystem::path &file)
{
    std::ifstream stream(file);

    if (not stream) {
        throw std::invalid_argument("failed to open file: " + file.string());
    }

    return stream;
}

【问题讨论】:

  • 我个人一直使用路径,因为这样我可以检查类型,例如“.txt”
  • 我担心这个问题会因为过于宽泛或基于意见而被关闭。尽管如此,我还是更喜欢第二个版本,因为它更加灵活(它可以与其他流一起使用),并且遵守 SRP。事实上,我会考虑更进一步,并基于 API 迭代器。
  • @user58697 处理文件时基于迭代器?
  • @Mac 是的,为什么不呢?例如,请参阅en.cppreference.com/w/cpp/iterator/istream_iterator
  • 您的辅助函数必须return std::move(stream); 否则无法编译。

标签: c++ architecture


【解决方案1】:

您自己表示可以添加“帮助器”功能以保留 istream 接口。这在可测试性方面也是更好的解决方案,并且遵循单一职责原则(SRP)。

您的辅助函数有一个职责(从文件创建流),而您的实际函数有另一个职责(它“做某事”:))。

我要补充一点,这取决于某事实际做了什么的上下文。例如,如果它是一个facade 用于对底层功能的不同访问,那么让该接口与实际路径具有意义。你仍然会有一个单独的辅助函数和一个从外观使用的 do_something 函数。

【讨论】:

    【解决方案2】:

    你可以吃掉你的蛋糕:

    #include <fstream>
    #include <sstream>
    #include <utility>
    
    //
    // some booilerplate to allow use of a polymorphic temporary
    template<class Stream, std::enable_if_t<std::is_base_of<std::istream, Stream>::value> * = nullptr>
    struct stream_holder
    {
      stream_holder(Stream stream) : stream_(std::move(stream)) {}
      operator std::istream&() && { return stream_; }
      operator std::istream&() & { return stream_; }
    
      private:
      Stream stream_;
    };
    
    // helper function
    template<class Stream, std::enable_if_t<std::is_base_of<std::istream, Stream>::value> * = nullptr>
    auto with_this(Stream&& stream)
    {
      return stream_holder<std::decay_t<Stream>>(std::forward<Stream>(stream));
    }
    
    
    
    // express logic in terms of stream
    void do_something(std::istream& stream_ref);
    
    // utility functions to create various types of stream
    std::ifstream file_stream();
    std::stringstream string_stream();
    
    
    int main()
    {
    
      // * composability with succinct syntax
      // * lifetime automatically managed
      // * no repetitive boilerplate
      do_something(with_this(file_stream()));
      do_something(with_this(string_stream()));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-14
      • 2013-04-09
      相关资源
      最近更新 更多