web-dev-qa-db-ja.com

C ++ 17 std :: filesystem :: path?のネイティブパス区切りのバグ

#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のバグですか?

18
Garland

いいえ、バグではありません!

string() et al および c_str()/native() 内部パス名をネイティブパス名の形式。

ネイティブとはどういう意味ですか

MSの状態 、それは ISO/IEC TS 18822:2015 を使用します。最終草案では、§4.11でネイティブパス名の形式を次のように定義しています。

オペレーティングシステムに依存するパス名の形式は、ホストオペレーティングシステムで受け入れられます。

Windowsでは、native()はパスをstd::wstring()として返します。

Windowsでディレクトリ区切り文字としてバックスラッシュを強制的に使用する方法

標準では、用語 preferred-separator を定義しています( §8.1(パス名の形式の文法) も参照):

オペレーティングシステムに依存するディレクトリ区切り文字。

path::make_preferred を使用すると、パスを(その場で)優先区切り文字に変換できます。 Windowsでは、noexcept演算子があります。

なぜ心配しないでください

パスに関するMSドキュメント は、/\の使用法について述べています

Windows APIのファイルI/O関数は、名前をNTスタイルの名前に変換する一環として「/」を「\」に変換します。ただし、次のセクションで説明する「\?\」プレフィックスを使用する場合を除きます。

C++ファイルナビゲーションに関するドキュメント では、スラッシュ(新しいドラフトでは 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への明示的なパス変換は、ファイルシステムの相互作用にそのセパレーターの使用を強制するライブラリーを操作する場合にのみ必要です。

6
Roi Danton

おおまかに言って、コンパイラーのバグは、標準によって(明示的または暗黙的に)禁止されている動作、またはコンパイラーのドキュメントから逸脱した動作を示す場合に発生します。

標準では、ネイティブパス文字列の形式に制限はありませんが、基盤となるオペレーティングシステム(以下の引用)で形式が受け入れられることを除きます。どのようにしてそのような制限を課すことができますか?ホストOSがパスをどのように処理するかについては、この言語には何の意味もありません。自信を持ってそれを行うには、コンパイル先のすべてのターゲットを知る必要がありますが、これは明らかに実現不可能です。

[fs.class.path]

5 パス名は、パスの名前を表す文字列です。パス名は、一般的なパス名形式の文法([fs.path.generic])またはホストオペレーティングシステムが受け入れるオペレーティングシステム依存のネイティブパス名形式に従ってフォーマットされます。

(エンファシス鉱山)

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の存在は、ネイティブパスが必ずしも優先パスではないことを示しています。 zero-overheadの原則を検討してください。パスを常に優先パスにすることは、パスを処理するときにそのような面倒なことをする必要がない人々にオーバーヘッドを招くでしょう。

最後に、std::experimentalネームスペースは、ボックスに記載されているとおりです。最終的な標準化ライブラリが実験版と同じように動作することを期待したり、最終的な標準化ライブラリがまったく存在することを期待したりすることはできません。実験的なものを扱うとき、それはそれがそうである方法です。

9
Cássio Renan

それらのいずれか1つがプラットフォームで「ネイティブ」と見なされる可能性があるため、これらのオプションのいずれか1つが等しく有効です。 Filesystem APIは、プラットフォームに関係なく、「ネイティブ」バージョンが指定した文字列と同一であることを保証しません。また、「ネイティブ」文字列がネイティブディレクトリ区切り文字を使用するという保証もありません。

5
Nicol Bolas