2013-09-27 11 views
80

Mam klasę z członkiem unique_ptr.Jak używać niestandardowego deletera z członkiem std :: unique_ptr?

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

Pasek jest klasą innej firmy, która ma funkcję create() i funkcję destroy().

Gdybym chciał użyć std::unique_ptr z nim na samodzielny funkcji mogę zrobić:

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

Czy istnieje sposób to zrobić z std::unique_ptr jako członek klasy?

Odpowiedz

78

Zakładając, że create i destroy są bezpłatne funkcje (co wydaje się być przypadek z fragmentu kodu PO) ze następujących podpisów:

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

można napisać klasę Foo tak

class Foo { 

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

    // ... 

public: 

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

    // ... 
}; 

Zauważ, że nie musisz pisać żadnych lambda ani niestandardowych deleterów tutaj, ponieważ destroy jest już deleterem.

+86

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

3

Możesz po prostu użyć std::bind z funkcją niszczenia.

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

Ale oczywiście można również użyć lambda.

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

Wystarczy utworzyć klasę Deleter:

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

i dostarczyć go jako argumentu szablonu z unique_ptr. Nadal będziesz musiał zainicjować unique_ptr w swoich konstruktorów:

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

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

O ile mi wiadomo, wszystkie popularne C++ biblioteki zaimplementować to w prawidłowy sposób; ponieważ BarDeleter w rzeczywistości nie ma żadnego stanu, nie musi zajmować żadnego miejsca w unique_ptr.

+4

ta opcja jest jedyną, która działa z tablicami, std :: vector i innymi kolekcjami, ponieważ może używać konstruktora std :: unique_ptr z parametrem zero.inne odpowiedzi używają rozwiązań, które nie mają dostępu do tego konstruktora parametrów zerowych, ponieważ instancja Deleter musi być dostarczona podczas konstruowania unikalnego wskaźnika. Ale to rozwiązanie zapewnia klasę Deleter ('struct BarDeleter') na' std :: unique_ptr' ('std :: unique_ptr '), która pozwala konstruktorowi 'std :: unique_ptr' samodzielnie utworzyć instancję Deletera . tzn. następujący kod jest dozwolony 'std :: unique_ptr bar [10];' – DavidF

+5

Stworzyłbym typedef do łatwego użycia 'typedef std :: unique_ptr UniqueBarPtr' – DavidF

60

Można to zrobić czysto, używając lambda w C++ 11 (testowane w G ++ 4.8.2).

Biorąc pod uwagę to wielokrotnego użytku typedef:

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

Można napisać:

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

Na przykład, z FILE*:

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

Dzięki temu możesz uzyskać korzyści bezpiecznego czyszczenia w wyjątkowych warunkach z użyciem RAII, bez konieczności generowania hałasu próbnego/przechwytującego.

+1

To powinna być odpowiedź, imo. To piękniejsze rozwiązanie. Czy są jakieś wady, np. posiadające 'std :: function' w definicji lub podobne? – j00hi

+6

@ j00hi, moim zdaniem to rozwiązanie ma niepotrzebne obciążenie z powodu 'std :: function'. Lambda lub klasa niestandardowa, jak w zaakceptowanej odpowiedzi, mogą być odwrotnie przedstawione w przeciwieństwie do tego rozwiązania. Ale to podejście ma przewagę w przypadku, gdy chcesz wyizolować wszystkie implementacje w dedykowanym module. – magras

+2

Spowoduje to wyciek pamięci, jeśli wygeneruje konstruktor std :: function (co może się zdarzyć, jeśli lambda jest zbyt duża, aby zmieściła się wewnątrz obiektu std :: function). – Ivan

Powiązane problemy