为什么std :: filesystem提供了这么多非成员函数?

时间:2017-03-27 17:57:11

标签: c++ c++17 boost-filesystem std-filesystem

考虑一下 file_size。要获得我们将使用的文件大小

std::filesystem::path p = std::filesystem::current_path();
// ... usual "does this exist && is this a file" boilerplate
auto n = std::filesystem::file_size(p);

没有错,如果它是纯粹的C,但是已经被教导C ++是一种OO语言[我知道这是多范式,向我们的语言律师道歉:-)]只是感觉如此...... 。命令(不寒而栗)给我,在那里我期待对象

auto n = p.file_size();

代替。对于其他功能也是如此,例如resize_fileremove_file以及可能更多。

你知道为什么Boost和std::filesystem选择这种命令式而不是对象式的任何理由?有什么好处?提升mentions the rule(在最底层),但没有理由。

我在思考固有问题,例如p之后的remove_file(p)状态,或错误标志(带有附加参数的重载),但这两种方法都没有解决这些问题的优雅程度。

你可以观察到与迭代器类似的模式,现在我们可以(应该?)做begin(it)而不是it.begin(),但在这里我认为理由是更符合非修改next(it)等。

4 个答案:

答案 0 :(得分:11)

Filesystem库在filesystem::path类型之间有一个非常清晰的分离,它代表一个抽象路径名(甚至不是存在的文件的名称)和访问实际物理文件系统的操作,即在磁盘上读取+写入数据。

你甚至指出了对此的解释:

  

设计规则是纯粹的词法操作作为类路径成员函数提供,而操作系统执行的操作作为自由函数提供。

这就是原因。

理论上可以在没有磁盘的系统上使用filesystem::pathpath类只包含一串字符,允许操作该字符串,在字符集之间进行转换,并使用一些定义主机操作系统上文件名和路径名结构的规则。例如,它知道目录名在POSIX系统上由/分隔,在Windows上由\分隔。操纵path中保存的字符串是一个“词法操作”,因为它只是执行字符串操作。

称为“文件系统操作”的非成员函数完全不同。它们不只是使用只是一串字符的抽象path对象,它们执行访问文件系统的实际I / O操作(stat系统调用openreaddir等)。这些操作采用path参数来命名要操作的文件或目录,然后它们访问真实文件或目录。它们不只是在内存中操纵字符串。

这些操作取决于操作系统提供的用于访问文件的API,它们依赖于可能以完全不同的方式进行内存中字符串操作的硬件。磁盘可能已满,或者可能在操作完成之前被拔出,或者可能存在硬件故障。

这样看,当然file_size不是path的成员,因为它与路径本身无关。路径只是文件名的表示,而不是实际文件的表示。函数file_size查找具有给定名称的物理文件,并尝试读取其大小。这不是文件 name 的属性,它是文件系统上持久文件的属性。与内存中保存文件名称的字符串完全分开的东西。

换句话说,我可以拥有一个包含完整废话的path对象,例如filesystem::path p("hgkugkkgkuegakugnkunfkw"),这很好。我可以追加到那条路径,或者询问它是否有根目录等。但是如果它不存在,我就无法读取这样一个文件的大小。我可以有一个存在的文件的路径,但我没有权限访问,如filesystem::path p("/root/secret_admin_files.txt");,这也没关系,因为它只是一串字符。当我尝试使用文件系统操作函数访问该位置的某些内容时,我只会收到“权限被拒绝”错误。

由于path成员函数从不接触文件系统,因此它们永远不会因权限或不存在的文件而失败。这是一个有用的保证。

  

你可以用迭代器观察一个类似的模式,现在我们可以(应该?)开始(它)而不是it.begin(),但在这里我认为理由是更符合非 - 修改下一个(它)等。

不,这是因为它对数组(不能有成员函数)和类类型同样有效。如果你知道你正在处理的类似范围的东西是一个容器而不是数组,那么你可以使用x.begin()但是如果你正在编写通用代码并且不知道它是一个容器还是一个数组那么{{ 1}}适用于这两种情况。

这两件事的原因(文件系统设计和非成员范围访问功能)不是一些反OO偏好,它们是出于更合理,更实际的原因。基于其中任何一个都是糟糕的设计,因为对于喜欢OO的人来说,感觉感觉更好,或者对不喜欢OO的人感觉更好。

此外,当一切都是成员函数时,有些事情你不能做:

std::begin(x)

但如果struct ConvertibleToPath { operator const std::filesystem::path& () const; // ... }; ConvertibleToPath c; auto n = std::filesystem::file_size(c); // works fine file_size的成员:

path

答案 1 :(得分:11)

已经发布了几个很好的答案,但它们并没有解决问题的核心:所有其他条件相同,如果你可以实现一些免费,非朋友的功能,你总是应该这样做。< / p>

为什么?

因为免费的非朋友功能,没有特权访问状态。测试类比测试函数要困难得多,因为你必须让自己相信这个类&#39;无论调用哪个成员函数,甚至成员函数的组合,都会保持不变量。你拥有的会员/朋友功能越多,你就必须做的工作就越多。

免费功能可以独立推理和测试。因为他们没有特权访问类状态,所以他们不可能违反任何类不变量。

我不知道不变量和特权访问path允许的详细信息,但显然他们能够实现许多功能作为免费功能,并且他们做出了正确的选择并且这样做了

Scott Meyers brilliant article on this topic,给出&#34;算法&#34;是否使某个功能成为会员。

此处Herb Sutter bemoaning the massive interface of std::string。为什么?因为string接口的大部分都可以作为自由函数实现。有时使用它可能有点笨拙,但它更容易测试,推理,改进封装和模块化,为以前没有的代码重用打开机会等等。

答案 2 :(得分:1)

有几个原因(虽然有点推测,但我并没有非常密切地遵循标准化过程):

  1. 因为它基于boost::filesystem,这是以这种方式设计的。现在,你可以问“为什么boost::filesystem这样设计?”,这将是一个公平的问题,但鉴于它是,并且它看起来很多里程,它被接受到标准,几乎没有变化。其他一些Boost构造也是如此(尽管有时会有一些变化,但大多数情况下都是如此)。

  2. 设计类时的一个共同原则是“如果某个函数不需要访问类的受保护/私有成员,而是可以使用现有成员 - 那么你也不能将其作为成员。”虽然不是每个人都认为 - boost::filesystem的设计师似乎都这样做。

    std::string()的背景下,在p.file_size(); 的背景下,由C ++名人赫伯特·萨特(Hebert Sutter)在file_size(p); ,一个具有无数方法的“巨石”类的背景下进行讨论(以及论证)。

  3. 预计在C ++ 17中我们可能已经有了统一调用语法(参见Bjarne的Stroustrup高度可读Guru of the Week #84)。如果已经接受了标准,请致电

    select min(created_at) as weekstart, first_response_secs, created_at
    from tickets
    group by floor(julianday('2017-03-25) - julianday(created_at)) % 7 = 0
    order by weekstart
    

    相当于调用

    for (i in 40:46){
      eval(parse(text =
      paste0('df',i,'$`Measurement Date` <-
             as.Date(df',i,'$`Measurement Date`, format = "%Y-%m-%d")')
      ))
    }
    

    所以你可以选择你喜欢的任何东西。基本上

答案 3 :(得分:0)

除了其他人已经说过的。 人们对“非成员”方法不满意的原因之一是需要在API的前面键入std :: filesystem ::或使用using指令。 但是实际上您不必这样做,只需跳过API调用的命名空间,如下所示:

#include <iostream>
#include <filesystem>

int main()
{
    auto p = std::filesystem::path{"/bin/cat"};
    //notice file_size below has no namespace qualifiers
    std::cout << "Binary size for your /bin/cat is " << file_size(p);
}

工作得很好,因为由于ADL的缘故,函数名称也在其参数的命名空间中查找。

(实时示例https://wandbox.org/permlink/JrFz8FJG3OdgRwg9