【问题标题】:Native path separator bug in C++17 std::filesystem::path?C++17 std::filesystem::path 中的本机路径分隔符错误?
【发布时间】:2018-08-16 22:22:03
【问题描述】:

#include <experimental/filesystem> 升级到#include <filesystem> 时遇到问题。似乎std::filesystem::path::wstring 方法没有返回与experimental::filesystem 相同的字符串。我编写了以下包含输出结果的小测试程序。

#include <iostream>
#include <filesystem>
#include <experimental/filesystem>

namespace fs = std::filesystem;
namespace ex = std::experimental::filesystem;
using namespace std;

int main()
{
    fs::path p1{ L"C:\\temp/foo" };    
    wcout << "std::filesystem Native: " << p1.wstring() << "  Generic: " << p1.generic_wstring() << endl;

    ex::path p2{ L"C:\\temp/foo" };
    wcout << "std::experimental::filesystem Native: " << p2.wstring() << "  Generic: " << p2.generic_wstring() << endl;
}

/* Output:
std::filesystem Native: C:\temp/foo  Generic: C:/temp/foo
std::experimental::filesystem Native: C:\temp\foo  Generic: C:/temp/foo
*/

https://en.cppreference.com/w/cpp/filesystem/path/string

返回值

本地路径名格式的内部路径名, 转换为指定的字符串类型。

该程序在 Windows 10 上运行,并使用 Visual Studio 2017 版本 15.8.0 编译。我希望本机路径名是C:\temp\foo

问题:这是std::filesystem::path 中的错误吗?

【问题讨论】:

  • Windows 上的 C 运行时 API 是“易变的”,因为它们接受 ` \ ` 或 ` / ` 作为分隔符。 Win32 API 不太宽容,特别是如果您使用 Long UNC,并且需要使用 ` \ `。
  • 在 Visual Studio 中,&lt;filesystem&gt; 是 Microsoft 特定的,如 here 所述。
  • std::experimental 的部分观点是,您应该期望最终版本的行为有所不同,并愿意接受最终的差异。
  • 无论这是否是“错误”,您都可以调用path::make_preferred() 将任何前斜杠转换为反斜杠。
  • 这不是标准中的错误——问题是它是否是 MSVC 实现中的错误

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


【解决方案1】:

大致而言,当编译器出现标准禁止的行为(显式或隐式)或与所述编译器的文档不同的行为时,就会发生错误。

该标准对本地路径字符串的格式没有任何限制,只是该格式应该被底层操作系统接受(以下引用)。它怎么能施加这样的限制?该语言对主机操作系统如何处理路径没有发言权,要自信地做到这一点,它必须知道它可能编译到的每一个目标,这显然是不可行的。

[fs.class.path]

5   路径名是表示路径名称的字符串。 路径名根据通用路径名格式语法 ([fs.path.generic]) 或根据主机操作系统接受的依赖于操作系统的本机路径名格式进行格式化。

(强调我的)

The documentation of MSVC 表示正斜杠完全可以用作分隔符:

这两个系统的共同点是,一旦超过了根名称,就会强加给路径名的结构。对于路径名 c:/abc/xyz/def.ext:

  • 根名称是c:
  • 根目录为/
  • 根路径为c:/
  • 相对路径为abc/xyz/def.ext
  • 父路径是c:/abc/xyz
  • 文件名为def.ext
  • 主干是def
  • 分机号是.ext

它确实提到了一个首选分隔符,但这实际上只暗示std::make_preferred 的行为,而不是默认路径输出的行为:

一个小的区别是路径名中目录序列之间的首选分隔符。两种操作系统都允许您编写正斜杠 /,但在某些情况下,Windows 更喜欢使用反斜杠 \

那么,这是否是错误的问题很简单:由于标准对行为没有施加任何限制,并且编译器的文档暗示没有强制需要反斜杠,因此不可能有错误。

剩下的问题是这是否是一个实施质量问题。毕竟,编译器和库实现者应该知道他们的目标的所有怪癖,并相应地实现特性。

您应该在 Windows 中使用哪个斜线('\''/'),或者它是否真的很重要,所以没有权威的答案。任何主张其中一个或另一个的答案都必须非常小心,不要过于基于意见。此外,path::make_preferred 的存在表明本机路径不一定是首选路径。考虑零开销原则:让路径始终是首选路径会给那些在处理路径时不需要那么迂腐的人带来开销。

最后,std::experimental 命名空间就是它在包装盒上所说的:您不应该期望最终标准化库的行为与其实验版本相同,或者甚至期望最终标准化库将存在。处理实验性的东西时就是这样。

【讨论】:

  • 展示错误的方式比您暗示的要多得多。尽管不是标准一致性问题,但误导性诊断、优化失败和其他此类事情都是错误。不遵循最不可能出乎意料的路径就像错误一样有错误。
  • @n.m.所有这些都是 QoI 问题。当然,它们可能会(正确地)在错误跟踪器中报告以进行修复,但是,它们是否真的是错误还有待商榷,因为它们并不会真正导致错误行为。
  • @n.m.话虽如此,我通过添加有关 MSVC 的特定信息改进了这篇文章。我希望你发现它现在好多了。
【解决方案2】:

不,这不是错误!

string()et alc_str()/native()native 路径名格式返回内部路径名。

原生是什么意思

MS states,它使用ISO/IEC TS 18822:2015。最终草案在第 4.11 节中定义了 本机路径名格式,如下所示:

依赖于操作系统的路径名格式主机操作系统接受

在 Windows 中,native() 将路径返回为std::wstring()

如何在 Windows 中强制使用反斜杠作为目录分隔符

标准定义了术语 preferred-separator(另见§8.1 (pathname format grammar)):

依赖于操作系统的目录分隔符。

可以使用path::make_preferred 将路径转换(就地)为首选分隔符。在 Windows 中,它具有 noexcept 运算符。

为什么你不应该担心

MS documentation about paths 说明了 /\ 的用法

Windows API 中的文件 I/O 函数将“/”转换为“\”作为将名称转换为 NT 样式名称的一部分,除非使用“\?\”前缀,如以下部分所述。

documentation about C++ file navigation 中,斜线(在较新的草案中称为fallback-separator)甚至直接在root-name 之后使用:

path pathToDisplay(L"C:/FileSystemTest/SubDir3/SubDirLevel2/File2.txt ");

带有-std:C++17的VS2017 15.8示例:

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;

void output(const std::string& type, fs::path& p)
{
    std::cout
        << type << ":\n"
        << "- native: " << p.string() << "\n"
        << "- generic: " << p.generic_string() << "\n"
        << "- preferred-separator" << p.make_preferred() << "\n";
}

int main()
{
    fs::path local_win_path("c:/dir/file.ext");
    fs::path unc_path("//your-remote/dir/file.ext");

    output("local absolute win path", local_win_path);
    output("unc path", unc_path);

    unc_path = "//your-remote/dir/file.ext"; // Overwrite make_preferred applied above.
    if (fs::is_regular_file(unc_path))
    {
        std::cout << "UNC path containing // was understood by Windows std filesystem";
    }
}

可能的输出(当 unc_path 是现有遥控器上的现有文件时):

local absolute win path:
- native: c:/dir/file.ext
- generic: c:/dir/file.ext
- preferred-separator"c:\\dir\\file.ext"
unc path:
- native: //your-remote/dir/file.ext
- generic: //your-remote/dir/file.ext
- preferred-separator"\\\\your-remote\\dir\\file.ext"
UNC path containing // was understood by Windows std filesystem

因此,只有在使用强制使用该分隔符进行文件系统交互的库时,才需要对 preferred-separator 进行显式路径转换。

【讨论】:

    【解决方案3】:

    其中任何一个都可以被视为平台上的“原生”,因此其中任何一个选项都同样有效。文件系统 API 不保证“本机”版本与您提供的字符串相同,无论平台如何。也不能保证“本机”字符串仅在通用“/”字符与其等效时才使用本机目录分隔符。

    【讨论】:

    • 许多但并非所有 Windows API 都接受“/”。例如,shell path handling functions 只接受“\”。
    • @zett42:是的,但是这些是否真的是操作系统的一部分还很模糊。确实,它们带有一个随操作系统提供的实用程序(explorer.exe shell),但如果您更改 shell,它不会不再是 Windows。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-14
    • 2019-11-06
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    • 2019-07-04
    相关资源
    最近更新 更多