2014-07-07 18 views
12

Argomento correlatoOne-liner per RAII su non puntatore?

std::unique_ptr, deleters and the Win32 API

Per utilizzare un manico Win32 come Raii, posso utilizzare la seguente riga

std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&CloseHandle)> m_mutex(CreateMutex(NULL, FALSE, NULL), &::CloseHandle); 

Per me questo è un ambiente pulito one-liner e fa esattamente quello che Voglio.

Quando si tratta di SOCKET, non verrà compilato con questa stessa riga poiché SOCKET non può essere nullptr.

Cosa devo fare per farlo funzionare è la seguente:

struct SocketDeleter 
{ 
    typedef SOCKET pointer; 

    void operator()(SOCKET h) 
    { 
     ::closesocket(h); 
    } 
}; 

// Start listen socket. 
std::unique_ptr<SOCKET, SocketDeleter> sock(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP)); 

Quello che non mi piace in questa implementazione è che ogni diverso tipo di ressources mi vorrà utilizzare, io è necessario copiare/incollare lo stesso codice per modificare solo la funzione di chiusura.

ho potuto utilizzare una macro, ma questo è davvero brutto e non può essere utilizzato due volte

#define RAII_UNIQUE_RESOURCE(varName, classType, init, closure) \ 
struct deleterMacro            \ 
{                \ 
    typedef classType pointer;         \ 
    void operator()(classType h)        \ 
    {               \ 
     closure(h);            \ 
    }               \ 
};                \ 
std::unique_ptr<classType, deleterMacro> varName(init); 

// Compile, but breaks as soon as 2 sockets defined. 
RAII_UNIQUE_RESOURCE(sock, SOCKET, socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP), ::closesocket); 

ho provato ad utilizzare un modello, ma non posso passare il mio puntatore a funzione alla funzione dell'operatore(), per quanto ne so.

template<class T, class methodDeclaration, class pFuncPointer> 
struct deleter 
{ 
    typedef T pointer; 

    void operator()(T h) 
    { 
     // Is there a way?? 
     methodDeclaration toCall = pFuncPointer; 
     toCall(h); 
    } 
}; 
// With a call such as ... 
std::unique_ptr<SOCKET, deleter<SOCKET, std::function<decltype(::closesocket)>, ::closesocket>> sock2(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP)); 
+1

probabilmente si vorrà qualcosa di simile: 'template struct stateless_deleter {utilizzando il puntatore = T; void operator() (T x) {Deleter (x); }}; 'Usage:' std :: unique_ptr p (OpenSocket()); ' –

+1

Perché preoccuparsi di' std :: unique_ptr'? Ci vorranno 5 minuti per scrivere un wrapper RAII o 30 secondi su Google. – Drop

+1

@Drop perché scrivere una funzionalità personalizzata ** se ** ce n'è uno standard? – UmNyobe

risposta

2

Infine, voglio con un'altra risposta Kerrek SB. È la proposta per la risorsa unica STD.

#ifndef UNIQUE_RESOURCE_H_ 
#define UNIQUE_RESOURCE_H_ 

#include <type_traits> 

// From standard proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3949.pdf 
// Slightly modified to compile on VS2012. 
namespace std 
{ 
    namespace experimental 
    { 
     enum class invoke_it { once, again }; 
     template<typename R,typename D> 
     class unique_resource_t 
     { 
      R resource; 
      D deleter; 
      bool execute_on_destruction; // exposition only 
      unique_resource_t& operator=(unique_resource_t const &); 
      unique_resource_t(unique_resource_t const &); // no copies! 
     public: 
      // construction 
      explicit unique_resource_t(R && resource, D && deleter, bool shouldrun=true) 
       : resource(std::move(resource)) 
       , deleter(std::move(deleter)) 
       , execute_on_destruction(shouldrun) 
      { 

      } 
      // move 
      unique_resource_t(unique_resource_t &&other) /*noexcept*/ 
       :resource(std::move(other.resource)) 
       ,deleter(std::move(other.deleter)) 
       ,execute_on_destruction(other.execute_on_destruction) 
      { 
        other.release(); 
      } 
      unique_resource_t& operator=(unique_resource_t &&other) 
      { 
       this->invoke(invoke_it::once); 
       deleter=std::move(other.deleter); 
       resource=std::move(other.resource); 
       execute_on_destruction=other.execute_on_destruction; 
       other.release(); 
       return *this; 
      } 
      // resource release 
      ~unique_resource_t() 
      { 
       this->invoke(invoke_it::once); 
      } 
      void invoke(invoke_it const strategy = invoke_it::once) 
      { 
       if (execute_on_destruction) { 
        try { 
         this->get_deleter()(resource); 
        } catch(...){} 
       } 
       execute_on_destruction = strategy==invoke_it::again; 
      } 
      R const & release() /*noexcept*/{ 
       execute_on_destruction = false; 
       return this->get(); 
      } 
      void reset(R && newresource) /*noexcept*/ { 
       invoke(invoke_it::again); 
       resource = std::move(newresource); 
      } 
      // resource access 
      R const & get() const /*noexcept*/ { 
       return resource; 
      } 
      operator R const &() const /*noexcept*/ 
      { 
       return resource; 
      } 
      R operator->() const /*noexcept*/ 
      { 
       return resource; 
      } 

//    Couldn't make this function compile on VS2012 
//    std::add_lvalue_reference<std::remove_pointer<R>::type>::type operator*() const 
//    { 
//      return * resource; 
//    } 

      // deleter access 
      const D & get_deleter() const /*noexcept*/ 
      { 
       return deleter; 
      } 
     }; 

     //factories 
     template<typename R,typename D> 
     unique_resource_t<R,D> unique_resource(R && r,D t) /*noexcept*/ 
     { 
       return unique_resource_t<R,D>(std::move(r), std::move(t),true); 
     } 
      template<typename R,typename D> 
     unique_resource_t<R,D> 
      unique_resource_checked(R r, R invalid, D t) /*noexcept*/ { 
       bool shouldrun = (r != invalid); 
       return unique_resource_t<R,D>(std::move(r), std::move(t), shouldrun); 
     } 
    } 
} 
#endif /* UNIQUE RESOURCE H */ 

Uso

auto listenSocket = std::experimental::unique_resource_checked(socket(AF_INET,SOCK_STREAM,IPPROTO_TCP), INVALID_SOCKET, closesocket); 

Spero che questo rende std abbastanza presto!

4

Kerrek SB ha risposto nei commenti ed era esattamente quello che stavo cercando!

template <typename T, typename D, D Deleter> 
struct stateless_deleter 
{ 
    typedef T pointer; 

    void operator()(T x) 
    { 
     Deleter(x); 
    } 
}; 
std::unique_ptr<SOCKET, stateless_deleter<SOCKET, int(*)(SOCKET), &::closesocket>> listenSocket(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP)); 
+0

Ohh, non mi sono reso conto che 'unique_ptr' ha controllato un criterio di tipo' pointer' dal suo deleter. Neat. – aschepler

+2

Ma questo è valido solo se 'T' è un tipo di puntatore. In caso contrario, non soddisferà i requisiti 'unique_ptr' di _NullablePointer_. E IIRC, Windows 'SOCKET' è un typedef di' int' o 'long', e quelli non si qualificano. – rodrigo

7

E 'ben noto l'esempio da Raii un FILE* utilizzando std::unique_ptr:

struct FILEDeleter 
{ 
    typedef FILE *pointer; 
    void operator()(FILE *fp) { fclose(fp); } 
}; 

typedef std::unique_ptr<FILE, FILEDeleter> FilePtr; 

FilePtr f(fopen("file.txt", "r")); 

Ahimè, un approccio simile a POSIX close() a RAII un descrittore di file non è possibile:

struct FDDeleter 
{ 
    typedef int pointer; 
    void operator()(int fd) { close(fp); } 
}; 

typedef std::unique_ptr<int, FDDeleter> FD; 

Sebbene alcuni compilatori funzionino correttamente, non è valido perché fd==0 è un descrittore di file valido! Quello nullo dovrebbe essere -1. Ma comunque, anche se fosse 0 non è ancora valido, perché FDDeleter::pointer devono soddisfare i requisiti di NullablePointer (riassumendo):

  1. Essa è paragonabile a nullptr.
  2. Deve essere inizializzato a valore su un valore che è pari a nullptr.

Così è nato UniqueHandle!

#include <memory> 

template <typename T, T TNul = T()> 
class UniqueHandle 
{ 
public: 
    UniqueHandle(std::nullptr_t = nullptr) 
     :m_id(TNul) 
    { } 
    UniqueHandle(T x) 
     :m_id(x) 
    { } 
    explicit operator bool() const { return m_id != TNul; } 

    operator T&() { return m_id; } 
    operator T() const { return m_id; } 

    T *operator&() { return &m_id; } 
    const T *operator&() const { return &m_id; } 

    friend bool operator == (UniqueHandle a, UniqueHandle b) { return a.m_id == b.m_id; } 
    friend bool operator != (UniqueHandle a, UniqueHandle b) { return a.m_id != b.m_id; } 
    friend bool operator == (UniqueHandle a, std::nullptr_t) { return a.m_id == TNul; } 
    friend bool operator != (UniqueHandle a, std::nullptr_t) { return a.m_id != TNul; } 
    friend bool operator == (std::nullptr_t, UniqueHandle b) { return TNul == b.m_id; } 
    friend bool operator != (std::nullptr_t, UniqueHandle b) { return TNul != b.m_id; } 

private: 
    T m_id; 
}; 

Il suo uso è abbastanza facile, vede meglio con un esempio:

struct FDDeleter 
{ 
    typedef UniqueHandle<int, -1> pointer; 
    void operator()(pointer p) 
    { 
     close(p); 
    } 
}; 
typedef std::unique_ptr<int, FDDeleter> FD; 

FD fd(open("test.txt", O_RDONLY)); 

Se si vuole veramente una battuta si potrebbe andare con questa generalizzazione:

template <typename T, T TNul = T(), typename RD, RD (*D)(T)> 
struct OLDeleter 
{ 
    typedef UniqueHandle<T, TNul> pointer; 
    void operator()(pointer p) 
    { 
     D(p); 
    } 
}; 

E poi solo una riga:

std::unique_ptr<int, OLDeleter<int, -1, int, close> > FD fd(open("test.txt", O_RDONLY)); 

Il problema è che devi aggiungere il ritorno di close() come argomento del modello e assumere che non ci sia nulla di divertente in questa funzione che impedisce la sua conversione in un int(*)(int) (strane convenzioni di chiamata, parametri extra, macro ...) e che è abbastanza scomodo

Si potrebbe aggiungere una funzione wrapper:

void my_close(int fd) { close(fd); } 

Ma se siete in esso, si potrebbe pure scrivere l'intero struct FDDeleter.

1

Un approccio leggermente diverso (all'interno dei presupposti dell'idioma RAII) consiste nell'utilizzare l'uscita dello boost boost.

Esempio:

#include <boost/scope_exit.hpp> 
#include <cstdlib> 
#include <cstdio> 
#include <cassert> 

int main() 
{ 
    std::FILE* f = std::fopen("example_file.txt", "w"); 
    assert(f); 

    BOOST_SCOPE_EXIT(f) { 
    // Whatever happened in scope, this code will be 
    // executed and file will be correctly closed. 
     std::fclose(f); 
    } BOOST_SCOPE_EXIT_END 

    // Some code that may throw or return. 
    // ... 
} 

Utilizzando this funzionalità, si sarebbe praticamente specificando freestanding "azioni RAII distruttore". Usa dove rende il tuo codice più chiaro e pulito ed evita che tutte le funzionalità siano incorporate più facilmente (o lo siano già) all'interno di un distruttore di classe.


Sembra che soonfunzionalità aggiuntive RAII verrà aggiunto alla lingua. Se disponibile, puoi utilizzare qualcosa come scoped_resource che assomiglia a this (mi riferirei a quel collegamento per un'implementazione completa di quello che chiedi)

3

Io uso spesso questo in C++ 11:

#include <utility> 

namespace{ 
    template<typename F> 
    struct RAII_Helper{ 
     template<typename InitFunction> 
     RAII_Helper(InitFunction &&init, F &&exit) : f_(std::forward<F>(exit)), canceled(false){ 
      init(); 
     } 
     RAII_Helper(F &&f) : f_(f), canceled(false){ 
     } 
     ~RAII_Helper(){ 
      if (!canceled) 
       f_(); 
     } 
     void cancel(){ 
      canceled = true; 
     } 
    private: 
     F f_; 
     bool canceled; 
    }; 
} 
template<class F> 
RAII_Helper<F> RAII_do(F &&f){ 
    return RAII_Helper<F>(std::forward<F>(f)); 
} 

template<class Init, class Exit> 
RAII_Helper<Exit> RAII_do(Init &&init, Exit &&exit){ 
    return RAII_Helper<Exit>(std::forward<Init>(init), std::forward<Exit>(exit)); 
} 

utilizzo:

FILE *f = fopen("file", "r"); 
if (!f) 
    return "error"; 
auto filecloser = RAII_do([=]{fclose(f);}); 

//also makes for good init/exit objects 
static auto initExit = RAII_do([]{initializeLibrary();}, []{exitLibrary();}); 

mi piace perché funziona per codice arbitrario, non solo i puntatori o maniglie. Anche la funzione di annullamento potrebbe essere omessa se non utilizzata mai.

0

Ecco una possibile soluzione utilizzando come esempio l'API NetCDF C, che ha interi semplici come maniglie:

retval = nc_open(..., &id); 
... // validate 
std::unique_ptr<int, void(*)(int*)> always_be_closing(&id, [](int* p){nc_close(*p);}); 

Naturalmente si può controllare il valore della lambda, se necessario.

... [](int* p){ if(p) nc_close(*p); } 

E un typedef lo rende un po 'più bello:

typedef std::unique_ptr<int, void(*)(int*)> nc_closer; 
... 
nc_closer abc(&id, [](int* p){nc_close(*p);}); 

E probabilmente si desidera una funzione per ridurre la duplicazione:

static void nc_close_p(int* p) { nc_close(*p); } 
... 
nc_closer abc(&id, &nc_close_p); 

o:

auto abc = auto_nc_close(&id, &nc_close_p); 

Dal unique_ptr implem Ent operator bool, è anche possibile utilizzare questo come un ambito blocco, come using in C#:

if (auto abc = auto_nc_close(&id, &nc_close_p)) 
{ 
    ... 
}