2013-08-19 28 views
13

Attualmente io uso questo codice per verificare se il file esiste sul Windows e POSIX sistemi operativi compatibili (Linux, Android, MacOS, iOS, BlackBerry 10):Come verificare se il file esiste in C++ in modo portatile?

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; 
} 

Domande:

  1. Questo codice ha qualche insidia? (forse un sistema operativo in cui non può essere compilato)

  2. È possibile farlo in modo veramente portatile utilizzando solo la libreria standard C/C++?

  3. Come migliorare? Alla ricerca di un esempio canonico.

+1

Qual è lo scopo di verificare se esiste, per esempio hai intenzione di aprire il file se esiste, o di stampare un messaggio di errore, o qualcos'altro? –

+4

Dovrebbe funzionare bene. Verifico specificamente sia per Windows che per POSIX con l'impostazione predefinita come qualcosa di POSIX. Probabilmente si dovrebbe definire anche un sistema operativo specifico per il progetto, poiché questi stessi nomi potrebbero cambiare da sistema a sistema. – Jiminion

+0

@MatsPetersson: stampare un messaggio di errore è uno dei casi da utilizzare. –

risposta

20

causa C++ è anche etichettato, userei boost::filesystem:

#include <boost/filesystem.hpp> 

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

Dietro le quinte

A quanto pare, spinta sta usando stat su POSIX e DWORD attr(::GetFileAttributesW(FileName)); su Windows (Nota: I ho estratto qui le parti rilevanti del codice, potrebbe essere che ho fatto qualcosa di sbagliato, ma dovrebbe essere così).

Fondamentalmente, oltre al valore di ritorno, boost sta controllando il valore di errno per verificare se il file non esiste realmente o se la tua statistica non è riuscita per un motivo diverso.

#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 viene definito separatamente per Windows e per POSIX:

di 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 per Boost. Ma usare Boost solo per questa funzione (non usiamo Boost per nient'altro) è un po 'avido. –

+1

Forse puoi espandere la tua risposta spiegando cosa c'è all'interno di '' boost :: filesystem :: exists''? Può essere una buona risposta. –

+1

@SergeyK. Lo farò grazie. –

3

mi piace perosnally provare solo per aprire il file :

dovrebbe funzionare su tutto ciò che ha file [non richiesto dallo standard C++] e dal momento che sta usando C++ std::string, non vedo perché std::ifstream dovrebbe essere un problema.

+8

Aprire un file è una cattiva idea - può esistere ma aprire con un altro processo in un modo non condiviso. –

+2

Non sono sicuro che esista un metodo al 100% infallibile: il file potrebbe anche esistere in questo momento e verrà eliminato la prossima volta che questo processo verrà eseguito. Oppure può appartenere a un altro utente, quindi non abbiamo i diritti per aprirlo (o 'stat', ecc.). Qualsiasi metodo "esiste" è di consulenza al meglio. Se la conoscenza dell'esistenza è "per evitare di salvare su un file esistente", l'impossibilità di aprire il file non è un problema, perché non è possibile aprire il file per scrivere una riga o tre più tardi [a parte dalle condizioni di gara, ovviamente - con qualsiasi metodo avrà quello]. –

+1

Ma la domanda era come migliorare il codice esistente. Il tuo codice rimuove '' # ifdef'' ma aggiunge un'altra ipotesi. Questo non è un miglioramento, è un compromesso. –

1
  1. Questo codice presenta qualche insidie? (Forse un sistema operativo in cui essa non può essere compilato)

Result == 0 "salta" ENAMETOOLONG, ELOOP, errori ecc come per this

posso pensare a questo: ENAMETOOLONG percorso è troppo lungo, come: -

In molti casi, durante una scansione ricorsiva, la sottocartella/le directory continuano ad aumentare, se il percorso è "troppo lungo" può causare questo errore, ma il file esiste ancora!

casi simili possono verificarsi anche con altri errori.

Inoltre,

Secondo this, Se mai preferiscono utilizzare il sovraccarico boost::filesystem::exists metodo

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

+1

+1 per un trabocchetto. Ma come superare questo? –

+1

@SergeyK. Non sono sicuro, potremmo provare a cambiare la directory su ogni recurs e quindi avviare una scansione. – P0W

+1

@SergeyK. Guarda come ha fatto il boost, in pratica, oltre al valore di ritorno, devi controllare l'ultimo codice di errore impostato. –