2013-09-27 13 views
80

Ho una classe con un membro unique_ptr.Come utilizzare un deleter personalizzato con un membro std :: unique_ptr?

class Foo { 
private: 
    std::unique_ptr<Bar> bar; 
    ... 
}; 

La barra è una classe di terze parti che ha una funzione create() e una funzione destroy().

Se volessi usare un std::unique_ptr con esso in una funzione stand-alone ho potuto fare:

void foo() { 
    std::unique_ptr<Bar, void(*)(Bar*)> bar(create(), [](Bar* b){ destroy(b); }); 
    ... 
} 

C'è un modo per fare questo con std::unique_ptr come membro di una classe?

risposta

78

Supponendo che create e destroy sono funzioni libere (che sembra essere il caso dal frammento di codice del PO) con le seguenti firme:

Bar* create(); 
void destroy(Bar*); 

è possibile scrivere la classe Foo come questo

class Foo { 

    std::unique_ptr<Bar, void(*)(Bar*)> ptr_; 

    // ... 

public: 

    Foo() : ptr_(create(), destroy) { /* ... */ } 

    // ... 
}; 

Si noti che non è necessario scrivere alcun lambda o deleter personalizzato qui perché destroy è già un deleter.

+86

Con C++ 11 'std :: unique_ptr ptr_;' – Joe

3

È possibile utilizzare semplicemente std::bind con una funzione di eliminazione.

std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy, 
    std::placeholders::_1)); 

Ma ovviamente è anche possibile utilizzare un lambda.

std::unique_ptr<Bar, std::function<void(Bar*)>> ptr(create(), [](Bar* b){ destroy(b);}); 
33

Hai solo bisogno di creare una classe di delezione:

struct BarDeleter { 
    void operator()(Bar* b) { destroy(b); } 
}; 

e fornirlo come argomento modello di unique_ptr. Avrai ancora per inizializzare l'unique_ptr nei tuoi costruttori:

class Foo { 
    public: 
    Foo() : bar(create()), ... { ... } 

    private: 
    std::unique_ptr<Bar, BarDeleter> bar; 
    ... 
}; 

Per quanto ne so, tutti i popolari librerie C++ implementano questa correttamente; poiché lo BarDeleter non ha alcun stato, non è necessario occupare spazio nello unique_ptr.

+4

questa opzione è l'unica che funziona con matrici, std :: vector e altre raccolte poiché può utilizzare il costruttore zero parametro std :: unique_ptr.altre risposte utilizzano soluzioni che non hanno accesso a questo costruttore di parametri zero poiché è necessario fornire un'istanza Deleter quando si costruisce un puntatore univoco. Ma questa soluzione fornisce una classe Deleter ('struct BarDeleter') a' std :: unique_ptr' ('std :: unique_ptr ') che consente al costruttore 'std :: unique_ptr' di creare un'istanza di Deleter da sola . vale a dire il seguente codice è consentito 'std :: unique_ptr bar [10];' – DavidF

+5

Vorrei creare un typedef per un facile utilizzo 'typedef std :: unique_ptr UniqueBarPtr' – DavidF

60

È possibile farlo in modo pulito utilizzando un lambda in C++ 11 (testato in G ++ 4.8.2).

Dato questo riutilizzabile typedef:

template<typename T> 
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>; 

si può scrivere:

deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); }); 

Ad esempio, con un FILE*:

deleted_unique_ptr<FILE> file(
    fopen("file.txt", "r"), 
    [](FILE* f) { fclose(f); }); 

Con questo si ottiene la vantaggi di una pulizia eccezionalmente sicura usando RAII, senza bisogno di provare/catturare il rumore.

+1

Questa dovrebbe essere la risposta, imo. È una soluzione più bella. O ci sono aspetti negativi, come ad es. avere 'std :: function' nella definizione o simile? – j00hi

+6

@ j00hi, a mio parere questa soluzione ha un sovraccarico non necessario a causa di 'std :: function'. Lambda o classe personalizzata come nella risposta accettata possono essere in linea a differenza di questa soluzione. Ma questo approccio ha un vantaggio nel caso in cui si voglia isolare tutta l'implementazione nel modulo dedicato. – magras

+2

Questa perdita di memoria si verifica se il costruttore std :: function genera (che potrebbe accadere se lambda è troppo grande per rientrare nell'oggetto std :: function) – Ivan

Problemi correlati