2010-03-12 13 views
15

Sono un dilettante C++. Sto scrivendo un codice API Win32 e ci sono maniglie e oggetti stranamente compositi in abbondanza. Quindi mi stavo chiedendo - c'è qualche classe di wrapper che renderebbe più facile la gestione delle risorse?Quale classe wrapper in C++ dovrei usare per la gestione automatizzata delle risorse?

Ad esempio, quando desidero caricare alcuni dati, apro un file con CreateFile() e ottengo un HANDLE. Quando avrò finito, dovrei chiamare lo CloseHandle() su di esso. Ma per ogni funzione di carico ragionevolmente complessa ci saranno dozzine di possibili punti di uscita, per non parlare delle eccezioni.

Quindi sarebbe bello se potessi avvolgere l'handle in una sorta di classe wrapper che chiamerebbe automaticamente CloseHandle() una volta che l'esecuzione ha lasciato l'oscilloscopio. Ancora meglio - potrebbe fare un conteggio dei riferimenti in modo che io possa passarlo dentro e fuori da altre funzioni, e rilascerebbe la risorsa solo quando l'ultimo riferimento ha lasciato l'ambito.

Il concetto è semplice, ma c'è qualcosa di simile nella libreria standard? Sto usando Visual Studio 2008, a proposito, e non voglio allegare un framework di terze parti come Boost o qualcosa del genere.

risposta

11

Scrivi il tuo. Sono solo poche righe di codice. È un compito talmente semplice che non è lo che vale la pena di fornire una versione generica riutilizzabile per il.

struct FileWrapper { 
    FileWrapper(...) : h(CreateFile(...)) {} 
    ~FileWrapper() { CloseHandle(h); } 

private: 
    HANDLE h; 
}; 

Pensa a ciò che una versione generica avrebbe dovuto fare: Sarebbe dovuto essere parametrizzabile in modo da poter specificare qualsiasi coppia di funzioni, e qualsiasi numero di argomenti a loro. L'istanziazione di un oggetto di questo tipo richiederebbe probabilmente più linee di codice della definizione di classe precedente.

Ovviamente, C++ 0x potrebbe in qualche modo ribaltare l'equilibrio con l'aggiunta di espressioni lambda. Due espressioni lambda possono essere facilmente passate a una classe wrapper generica, quindi una volta che C++ 0x supporta, possiamo visualizzare una classe RAII generica aggiunta a Boost o qualcosa del genere.

Ma al momento, è più facile rotolare il proprio ogni volta che ne hai bisogno.

Per quanto riguarda l'aggiunta del conteggio dei riferimenti, sconsiglierei di farlo. Il conteggio dei riferimenti è costoso (improvvisamente il tuo handle deve essere assegnato dinamicamente e i contatori di riferimento devono essere mantenuti in ogni incarico) e molto difficile da ottenere.È un'area che esplode in condizioni di gara sottili in un ambiente filettato.

Se fai bisogno di conteggio dei riferimenti, basta fare qualcosa di simile boost::shared_ptr<FileWrapper>: avvolgere le classi RAII ad hoc personalizzati in un shared_ptr.

+0

L'idea migliore, IMHO. Queste classi sono chiamate handle guards ... – SadSido

+2

Il codice non funziona poiché è possibile copiare la struttura. Http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization – Kerido

+2

@Kerido, forse, forse no. Dipende dalla semantica della risorsa che si sta avvolgendo. è giusto dare a Jalf il beneficio del dubbio e assumere che il codice postato sia solo un semplice esempio illustrativo. –

0

MFC ha alcune primitive adatte (ad esempio, CFile), ma non la libreria standard.

+0

Tale classe non sembra molto complessa. Forse c'è un'implementazione di esempio sul web da qualche parte che potrei copiare e incollare nella mia soluzione? Quali parole chiave dovrei usare in Google per questo? –

+0

Guarda questo ad esempio: http://bbdsoft.com/win32.html Prima corrispondenza per la query "CreateFile CloseHandle wrapper". – sharptooth

+0

Anche CFile e simili semplificano notevolmente le cose rispetto alla scrittura di tutto il codice con Win32 non elaborato. – sharptooth

2

In sostanza, fstream è un buon wrapper C++ per i file handle. Fa parte dello standard, il che significa che è portatile, ben testato ed estensibile in modo orientato agli oggetti. Per le risorse di file, è un grande concetto.

Tuttavia, fstream funziona solo per i file, non per le maniglie generiche, ossia le discussioni, i processi, gli oggetti di sincronizzazione, file mappati in memoria, ecc

+0

Ho usato solo gli handle di file come un comune esempio di facile comprensione. In pratica le cose sono ... più strane. –

+0

Quali maniglie intendi allora? – Kerido

+0

SSPI gestisce come CredHandle, CtxtHandle e SecBufferDesc. L'ultimo è una strana struttura che contiene una matrice allocata dinamicamente di strutture in cui ogni struct ha un puntatore a un buffer allocato dinamicamente. In poche parole, è una raccolta di dimensioni variabili di buffer di dimensioni variabili. La funzione di rilascio non è così banale come solo "cancella". :( –

0

Visual C++ 2008 supporta TR1 attraverso il Feature Pack, e TR1 comprende shared_ptr . Lo userei - è una classe di puntatore intelligente molto potente e può essere generalizzata per fare il tipo di gestione delle risorse che stai richiedendo.

TR1 è di fatto un'estensione dello standard. Credo che sia ancora ufficialmente "pre-standard", ma in effetti è possibile considerarlo bloccato.

+0

Nota che usando 'shared_ptr' per questo è necessario scrivere in alcuni casi una funzione di cancellazione personalizzata (in alcuni casi è sufficiente passare, ad esempio, la funzione 'CloseHandle' come deleter). – celticminstrel

0

Non credo che ci sia qualcosa nella libreria standard, e ho anche dubbi sul fatto che i puntatori condiviso (come in spinta) può essere usato (dato che quelli si aspetterebbero che il puntatore si comandi su HANDLE, non su HANDLE).

Non dovrebbe essere difficile scriverne uno da soli, seguendo l'idioma scope guard (e facendo uso di modelli/puntatori di funzione ecc. Se lo si desidera).

0
template <typename Traits> 
class unique_handle 
{ 
    using pointer = typename Traits::pointer; 

    pointer m_value; 

    auto close() throw() -> void 
    { 
     if (*this) 
     { 
      Traits::close(m_value); 
     } 
    } 

public: 

    unique_handle(unique_handle const &) = delete; 
    auto operator=(unique_handle const &)->unique_handle & = delete; 

    explicit unique_handle(pointer value = Traits::invalid()) throw() : 
     m_value{ value } 
    { 
    } 

    unique_handle(unique_handle && other) throw() : 
     m_value{ other.release() } 
    { 
    } 

    auto operator=(unique_handle && other) throw() -> unique_handle & 
    { 
     if (this != &other) 
     { 
      reset(other.release()); 
     } 

     return *this; 
    } 

    ~unique_handle() throw() 
    { 
     close(); 
    } 

    explicit operator bool() const throw() 
    { 
     return m_value != Traits::invalid(); 
    } 

    auto get() const throw() -> pointer 
    { 
     return m_value; 
    } 

    auto get_address_of() throw() -> pointer * 
    { 
     ASSERT(!*this); 
     return &m_value; 
    } 

    auto release() throw() -> pointer 
    { 
     auto value = m_value; 
     m_value = Traits::invalid(); 
     return value; 
    } 

    auto reset(pointer value = Traits::invalid()) throw() -> bool 
    { 
     if (m_value != value) 
     { 
      close(); 
      m_value = value; 
     } 

     return static_cast<bool>(*this); 
    } 

    auto swap(unique_handle<Traits> & other) throw() -> void 
    { 
     std::swap(m_value, other.m_value); 
    } 
}; 

template <typename Traits> 
auto swap(unique_handle<Traits> & left, 
    unique_handle<Traits> & right) throw() -> void 
{ 
    left.swap(right); 
} 

template <typename Traits> 
auto operator==(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() == right.get(); 
} 

template <typename Traits> 
auto operator!=(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() != right.get(); 
} 

template <typename Traits> 
auto operator<(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() < right.get(); 
} 

template <typename Traits> 
auto operator>=(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() >= right.get(); 
} 

template <typename Traits> 
auto operator>(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() > right.get(); 
} 

template <typename Traits> 
auto operator<=(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() <= right.get(); 
} 

struct null_handle_traits 
{ 
    using pointer = HANDLE; 

    static auto invalid() throw() -> pointer 
    { 
     return nullptr; 
    } 

    static auto close(pointer value) throw() -> void 
    { 
     VERIFY(CloseHandle(value)); 
    } 
}; 

struct invalid_handle_traits 
{ 
    using pointer = HANDLE; 

    static auto invalid() throw() -> pointer 
    { 
     return INVALID_HANDLE_VALUE; 
    } 

    static auto close(pointer value) throw() -> void 
    { 
     VERIFY(CloseHandle(value)); 
    } 
}; 

using null_handle = unique_handle<null_handle_traits>; 
using invalid_handle = unique_handle<invalid_handle_traits>; 
+3

È meglio aggiungere qualche descrizione alla risposta. – GMchris