2013-09-27 4 views
80

У меня есть класс с элементом unique_ptr.Как использовать пользовательский делетер с элементом std :: unique_ptr?

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

Bar - это класс третьей стороны, который имеет функцию create() и функцию destroy().

Если бы я хотел использовать std::unique_ptr с ним в автономной функции я мог бы сделать:

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

Есть ли способ сделать это с std::unique_ptr в качестве члена класса?

ответ

78

Предполагая, что create и destroy свободные функции (которые, как представляется, в случае с фрагмента кода в OP в) со следующими подписями:

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

Вы можете написать свой класс Foo как это

class Foo { 

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

    // ... 

public: 

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

    // ... 
}; 

Обратите внимание, что вам не нужно писать какие-либо лямбды или пользовательские делетеры здесь, потому что destroy уже является дебетером.

+86

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

3

Вы можете просто использовать std::bind с функцией уничтожения.

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

Но, конечно, вы также можете использовать лямбда.

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

Вам просто нужно создать класс DeleteR:

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

и предоставить его в качестве аргумента шаблона из unique_ptr. Вы все равно придется инициализировать unique_ptr в своих конструкторах:

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

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

Насколько я знаю, все популярные C++ библиотеки реализовать это правильно; поскольку BarDeleter на самом деле не имеет состояния, ему не нужно занимать какое-либо место в unique_ptr.

+4

Этот параметр является единственным, который работает с массивами, std :: vector и другими коллекциями, так как он может использовать нулевой параметр std :: unique_ptr constructor.другие ответы используют решения, которые не имеют доступа к этому конструктору нулевых параметров, потому что при построении уникального указателя должен быть предоставлен экземпляр Deleter. Но это решение предоставляет класс Deleter ('struct BarDeleter') для' std :: unique_ptr' ('std :: unique_ptr '), который позволяет конструктору 'std :: unique_ptr' создавать экземпляр Deleter самостоятельно , т. е. разрешен следующий код: std :: unique_ptr bar [10]; ' – DavidF

+5

Я бы создал typedef для удобного использования' typedef std :: unique_ptr UniqueBarPtr' – DavidF

60

Это можно сделать с помощью лямбда в C++ 11 (проверено в G ++ 4.8.2).

Учитывая это многоразовые typedef:

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

Вы можете написать:

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

Например, с FILE*:

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

При этом вы получите преимущества безопасной для исключения очистки с использованием RAII, без необходимости использования шумов try/catch.

+1

Это должен быть ответ, imo. Это более красивое решение. Или есть недостатки, например, например. с 'std :: function' в определении или так? – j00hi

+6

@ j00hi, на мой взгляд, это решение имеет лишние накладные расходы из-за 'std :: function'. В отличие от этого решения может быть встроен лямбда или пользовательский класс, как в принятом ответе. Но этот подход имеет преимущество в том случае, если вы хотите изолировать всю реализацию в выделенном модуле. – magras

+2

Это приведет к утечке памяти, если std :: function constructor выбрасывает (что может произойти, если лямбда слишком велика для размещения внутри объекта std :: function) – Ivan

Смежные вопросы