【问题标题】:Cross-platform way to handle std::string/std::wstring with std::filesystem::path使用 std::filesystem::path 处理 std::string/std::wstring 的跨平台方法
【发布时间】:2019-10-23 11:29:33
【问题描述】:

我有一段在 Linux 上引发异常的 C++ 代码示例:

namespace fs = std::filesystem;
const fs::path pathDir(L"/var/media");
const fs::path pathMedia = pathDir / L"COMPACTO - Diogo Poças.mxf" // <-- Exception thrown here

抛出的异常是:filesystem error: Cannot convert character sequence: Invalid in or incomplete multibyte or wide character

我推测该问题与ç 字符的使用有关。

  1. 为什么这个宽字符串 (wchar_t) 是“无效或不完整的多字节或宽字符”?
  2. 展望未来,我如何使相关代码跨平台运行在 Windows 和/或 Linux 上。
    • 是否有我需要使用的辅助函数?
    • 我需要从程序员的 PoV 中强制执行哪些规则?
    • 我在这里看到了response,上面写着“不要在 Linux 上使用宽字符串”,我是否对 Windows 使用相同的规则?

Linux 环境(别忘了我想跨平台运行):

  • Ubuntu 18.04.3
  • GCC 9.2.1
  • C++17

【问题讨论】:

    标签: character-encoding std-filesystem


    【解决方案1】:

    看起来像a GCC bug

    根据std::filesystem::path::path,您应该能够使用宽字符串调用 std::filesystem::path 构造函数,并且独立于底层平台(这就是 std::filesystem 的全部意义)。

    Clang 显示正确的行为。

    【讨论】:

    • 我在 bugzilla 中进行了快速搜索。是否已报告/修复?
    • 我也遇到了这个问题并在这里提交了一个错误报告:gcc.gnu.org/bugzilla/show_bug.cgi?id=95048 该示例也适用于 gcc 9.1.0。
    【解决方案2】:

    不幸的是,std::filesystem 在编写时并未考虑到操作系统兼容性,至少不像宣传的那样。

    对于基于 Unix 的系统,我们需要 UTF8(u8"string",或者只是 "string",具体取决于编译器)

    对于 Windows,我们需要 UTF16 (L"string")

    在 C++17 中,您可以使用 filesystem::u8path(由于某种原因在 C++20 中已弃用)。在 Windows 中,这会将 UTF8 转换为 UTF16。现在您可以将 UTF16 传递给 API。

    #ifdef _WINDOWS_PLATFORM
        //windows I/O setup
        _setmode(_fileno(stdin), _O_WTEXT);
        _setmode(_fileno(stdout), _O_WTEXT);
    #endif
    
    fs::path path = fs::u8path(u8"ελληνικά.txt");
    
    #ifdef _WINDOWS_PLATFORM
        std::wcout << "UTF16: " << path << std::endl;
    #else
        std::cout <<  "UTF8:  " << path << std::endl;
    #endif
    

    或使用您自己的宏为 Windows (L"string") 设置 UTF16,为基于 Unix 的系统设置 UTF8(u8"string" 或只是 "string")。确保为 Windows 定义了 UNICODE

    #ifdef _WINDOWS_PLATFORM
    #define _TEXT(quote) L##quote
    #define _tcout std::wcout
    #else
    #define _TEXT(quote) u8##quote
    #define _tcout std::cout
    #endif
    
    fs::path path(_TEXT("ελληνικά.txt"));
    _tcout << path << std::endl;
    

    另请参阅
    https://en.cppreference.com/w/cpp/filesystem/path/native


    注意,Visual Studio 有一个特殊的 std::fstream 构造函数,它允许使用 UTF16 文件名,并且它兼容 UTF8 读/写。例如,以下代码将在 Visual Studio 中工作:
    fs::path utf16 = fs::u8path(u8"UTF8 filename ελληνικά.txt");
    std::ofstream fout(utf16);
    fout << u8"UTF8 content ελληνικά";
    

    我不确定在 Windows 上运行的最新 gcc 版本是否支持。

    【讨论】:

    • 谢谢!它看起来像一个雷区!是否最好坚持使用 UTF-8,然后在需要时转换为其他字符编码/表示?
    • 这取决于您编写的程序类型。如果您的程序仅适用于 Linux,则仅使用 UTF8。如果您的代码同时在 Windows 和 Linux 上运行,则坚持使用 UTF8,并使用 UTF16 转换来进行 Windows API 调用。如果您的程序仅是 Windows,特别是 GUI 程序,那么您将完全使用 UTF16。另见编辑。
    • 感谢您的帮助!在从各地检索到零碎信息后,您帮我解决了很多问题。
    • u8path(...) 已被弃用,因为在 C++20 中,我们有不同的 u8string 和 char8_t* ,这意味着 UTF-8(与没有指定编码的纯字符串/char* 相反)。 std::filesystem::path 可以接受这些作为构造函数参数,使 u8path 多余。
    • 这个答案至少部分不正确。 std::filesystem 的编写考虑了操作系统的兼容性。 OP 遇到的问题是一个 gcc 错误。如果没有该错误,OP 的代码可能是在 C++17 中编写此类代码的最佳方式。在c++20中,可以用u8""代替L""。
    猜你喜欢
    • 2011-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-06
    • 2013-01-14
    • 1970-01-01
    • 2014-06-21
    • 1970-01-01
    相关资源
    最近更新 更多