2013-08-19 31 views
13

Obecnie używam tego kodu, aby sprawdzić, czy plik istnieje na Windows i POSIX -Kompatybilny systemów operacyjnych (Linux, Android, MacOS, iOS, BlackBerry 10):Jak sprawdzić, czy plik istnieje w C++ w sposób przenośny?

bool FileExist(const std::string& Name) 
{ 
#ifdef OS_WINDOWS 
    struct _stat buf; 
    int Result = _stat(Name.c_str(), &buf); 
#else 
    struct stat buf; 
    int Result = stat(Name.c_str(), &buf); 
#endif 
    return Result == 0; 
} 

pytania:

  1. Czy ten kod ma jakieś pułapki? (może system operacyjny, na którym nie można go skompilować)

  2. Czy można to zrobić w naprawdę przenośny sposób, używając tylko standardowej biblioteki C/C++?

  3. Jak to poprawić? Szukam przykładu kanonicznego.

+1

Co jest celem sprawdzenia, czy istnieje, na przykład czy otworzysz plik, jeśli istnieje, czy wydrukujesz komunikat o błędzie lub coś innego? –

+4

Powinno działać dobrze. W szczególności sprawdziłbym zarówno dla systemu Windows, jak i dla POSIX, z domyślną wartością podobną do POSIX. Prawdopodobnie powinieneś również zdefiniować system operacyjny dla konkretnego projektu, ponieważ same te nazwy mogą się zmieniać z systemu na system. – Jiminion

+0

@MatsPetersson: drukowanie komunikatu o błędzie jest jednym z przypadków użycia. –

Odpowiedz

20

Ponieważ C++ jest również określili, że użyłby boost::filesystem:

#include <boost/filesystem.hpp> 

bool FileExist(const std::string& Name) 
{ 
    return boost::filesystem::exists(Name); 
} 

za kulisami

Najwyraźniej doładowania jest z wykorzystaniem stat na POSIX i DWORD attr(::GetFileAttributesW(FileName)); w systemie Windows (uwaga: "Wyodrębniłem tutaj odpowiednie części kodu, może się zdarzyć, że zrobiłem coś złego, ale to powinno być to).

Zasadniczo, oprócz wartości zwracanej, boost sprawdza wartość errno, aby sprawdzić, czy plik naprawdę nie istnieje, lub czy twoja stata nie powiodła się z innego powodu.

#ifdef BOOST_POSIX_API 

struct stat path_stat; 
if (::stat(p.c_str(), &path_stat)!= 0) 
{ 
    if (ec != 0)       // always report errno, even though some 
    ec->assign(errno, system_category()); // errno values are not status_errors 

    if (not_found_error(errno)) 
    { 
    return fs::file_status(fs::file_not_found, fs::no_perms); 
    } 
    if (ec == 0) 
    BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", 
     p, error_code(errno, system_category()))); 
    return fs::file_status(fs::status_error); 
} 

#else 
    DWORD attr(::GetFileAttributesW(p.c_str())); 
    if (attr == 0xFFFFFFFF) 
    { 
     int errval(::GetLastError()); 
     if (not_found_error(errval)) 
     { 
      return fs::file_status(fs::file_not_found, fs::no_perms); 
     } 
    } 
#endif 

not_found_error jest definiowany osobno dla Windows i dla POSIX:

Windows:

bool not_found_error(int errval) 
    { 
    return errval == ERROR_FILE_NOT_FOUND 
     || errval == ERROR_PATH_NOT_FOUND 
     || errval == ERROR_INVALID_NAME // "tools/jam/src/:sys:stat.h", "//foo" 
     || errval == ERROR_INVALID_DRIVE // USB card reader with no card inserted 
     || errval == ERROR_NOT_READY // CD/DVD drive with no disc inserted 
     || errval == ERROR_INVALID_PARAMETER // ":sys:stat.h" 
     || errval == ERROR_BAD_PATHNAME // "//nosuch" on Win64 
     || errval == ERROR_BAD_NETPATH; // "//nosuch" on Win32 
    } 

POSIX:

bool not_found_error(int errval) 
    { 
    return errno == ENOENT || errno == ENOTDIR; 
    } 
+7

+1 za wzmocnienie. Ale użycie Boost tylko dla tej funkcji (nie używamy Boost do niczego innego) jest trochę chciwe. –

+1

Może możesz rozszerzyć swoją odpowiedź, wyjaśniając, co jest w środku '' boost :: filesystem :: exists''? To może być dobra odpowiedź. –

+1

@SergeyK. Zrobię, dzięki. –

3

I perosnally lubią po prostu spróbuj otworzyć plik :

bool FileExist(const std::string& Name) 
{ 
    std::ifstream f(name.c_str()); // New enough C++ library will accept just name 
    return f.is_open(); 
} 

powinien działać na cokolwiek, co ma pliki [Nie wymagane przez normy C++] a ponieważ używa C++ std::string, nie widzę dlaczego std::ifstream powinno być problemem.

+8

Otwarcie pliku to zły pomysł - może istnieć, ale otworzyć go innym procesem w niezamierzony sposób. –

+2

Nie jestem pewien, czy jest jakiś sposób, aby uzyskać 100% dowód na głupi - plik może również istnieć już teraz i zostać usunięty przy następnym uruchomieniu tego procesu. Lub może należeć do innego użytkownika, więc nie mamy uprawnień do otwierania (lub 'stat', etc). Każda metoda "istnieje" jest w najlepszym wypadku zalecana. Jeśli wiedza o tym, czy istnieje, ma na celu "unikanie zapisywania w istniejącym pliku", to niemożność otwarcia pliku nie stanowi problemu, ponieważ nie można również otworzyć pliku do napisania linii lub trzy później [na bok] oczywiście z warunków wyścigowych - dowolną metodą będzie to miało]. –

+1

Ale pytanie brzmiało, jak poprawić istniejący kod. Twój kod usuwa '' # ifdef'', ale dodaje inne założenie. To nie jest poprawa, to kompromis. –

1
  1. Czy ten kod ma jakieś pułapki? (Może OS gdzie nie może zostać skompilowany)

Result == 0 "przeskakuje" ENAMETOOLONG, ELOOP, błędy itd. Jak na this

mogę myśleć w ten sposób: ENAMETOOLONG ścieżka jest zbyt długa jak: -

W wielu przypadkach, podczas skanowania cyklicznego podkatalog/katalogi ciągle się powiększają, jeśli ścieżka jest "zbyt" długa, może to spowodować wystąpienie tego błędu, ale plik nadal istnieje!

Podobne przypadki mogą wystąpić również w przypadku innych błędów.

Również

Zgodnie this, I'ld wolą używać przeciążonej boost::filesystem::exists metoda

bool exists(const path& p, system::error_code& ec) noexcept;

+1

+1 za pułapkę. Ale jak przezwyciężyć to? –

+1

@SergeyK. Nie jestem pewien, możemy spróbować zmienić katalog na każdym powtórzeniu, a następnie rozpocząć skanowanie. – P0W

+1

@SergeyK. Zobacz, w jaki sposób doładowanie - w zasadzie - poza wartością zwracaną, musisz sprawdzić ostatni zestaw kodów błędów. –