2013-08-23 4 views
0

Рассмотрим некоторую функцию:Как я могу использовать unique_ptr с более общим делетером?

template<typename F> 
void foo(F f) { 
    std::unique_ptr<int> p = f(); 
    // do some stuff with p 
} 

Поскольку unique_ptr указы шаблона по умолчанию аргумент а, default_delete, для D, любой функциональный объект, передаваемый foo, который возвращает unique_ptr с непостоянным по умолчанию Deleter не удается скомпилировать. Например,

int x = 3; 
foo([&x](){ 
    // use empty deleter 
    return std::unique_ptr<int>(&x, [](int*){}); 
}); 

Однако я мог видеть это быть потенциально полезным, и я не вижу прямой причины, почему это не должно быть возможно. Существует ли общий подход к решению этой проблемы?

Редактировать

Простое исправление было бы определить foo вместо того, чтобы использовать следующее:

std::unique_ptr<int, std::function<void(int*)>> p = f(); 

Но я задаюсь вопросом, почему это не могло быть включено в интерфейс для unique_ptr ? Есть ли причина, по которой интерфейс класса не может предоставить этот общий атрибут? Существуют ли подходы для «обертывания» такого рода вещей в новое определение?

Например,

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

Но это кажется опасным, потому что он предоставляет возможность сделать что-то вроде follwing,

Generic_unique_ptr<int> p(new int()); 

, который оставил бы Deleter неинициализированным и демонстрируют неопределенное поведение. Возможно, каким-то образом предоставить экземплярstd::default_delete<T> как дефолт по умолчанию?

+0

@TemplateRex Я не уверен, что они одинаковы. Как можно вывести тип делеции из аргумента шаблона 'F'? – jwalk

ответ

4

Если все, что вы хотите сделать, это использовать указатель в функции, вы можете только использовать ключевое слово auto; компилятор выведет тип unique_ptr , который был использован и, таким образом, автоматически делать правильные вещи:

template <typename F> 
void foo(F f) 
{ 
    auto p = f(); 
    p->bar(); 
} 

Теперь, с вашего комментария, мы знаем, что это не все, что вы хотите, но вы хотите быть способный сохранить unique_ptr в вашем классе для работы с позже. Это создает множество совершенно различных задач:

  1. unique_ptr<T, D1> и unique_ptr<T, D2> различные типы. Таким образом, мы должны знать, что unique_ptr<T, D> будет возвращен вашим функтор F
  2. Даже если бы мы знали тип возвращаемого F заранее, наш класс все еще может хранить только unique_ptr<T, D1> и не unique_ptr<T, D2>.

Самый простой способ обойти это (что я могу думать, что может быть лучше путем) является типа стирания.

Мы создаем себе базовый класс, который предоставляет указатель управляемую unique_ptr:

template <typename T> 
struct wrapper 
{ 
    virtual ~wrapper() {} 
    virtual T const * get() const = 0; 
    virtual T * get() = 0; 
}; 

С этого класса наследует наш фактический класс хранения, который выводит тип из unique_ptr:

template <typename T, typename F> 
struct storage 
    : wrapper<T> 
{ 
    storage(F f) { p_ = f(); } 
    T const * get() const { return p_.get(); } 
    T * get() { return p_.get(); } 

    private: 
     typename std::result_of<F()>::type p_; 
}; 

В классе, в котором вы действительно заботитесь, теперь вы можете сохранить указатель на наш базовый класс и использовать полиморфизм для доступа к базовому объекту, в этом случае unique_ptr. Предположим, что мы переместили классы выше в namespace detail, чтобы скрыть их от пользователя:

template <typename T> 
class some_class 
{ 
    public: 
     template <typename F> 
     void store(F f) 
     { 
      storage_.reset(new detail::storage<T, F>(f)); 
     } 

     T const * get() const { return storage_->get(); } 
     T * get() { return storage_->get(); } 

    private: 
     std::unique_ptr<detail::wrapper<T>> storage_; 
}; 

Вы можете найти полностью рабочий пример here.

+0

Это фантастика! Я был виноват в том, что я не уделял этому ключевому слову много внимания, но он решительно решает эту проблему! – jwalk

+0

Это потрясающе. Я попытался сделать что-то похожее и потерпел неудачу. – jwalk

+0

@jwalk обновил мой ответ. – nijansen

2

Но мне интересно, почему это не могло быть включено в интерфейс для unique_ptr?

Поскольку сделать это вынудит все накладные расходы std::function «s на всех. unique_ptr предназначен для практически любого случая одиночного владения указателем. Вы платите за то, что используете; не все, кто использует пользовательские потребности в отсрочке, которые должны быть generic. Таким образом, им не нужно платить за это.

Кроме того, существующая методология позволяет обрабатывать ресурсы, отличные от указателей, поскольку дебит может точно указать, какой тип хранится в unique_ptr.

Если вы хотите предоставить эту общую конструкцию дебетера, вы можете создать класс, который (на частном уровне) наследует от unique_ptr и реплицирует его интерфейс, за вычетом конструктора, который не принимает экземпляр удаления. Таким образом, пользователь вынужден передать функцию делетера.

+0

Спасибо за прямой ответ. Я займусь этим. – jwalk

+0

+1 для почему, -1 для того, чтобы сказать, что кто-то должен наследовать от 'std :: unique_ptr': зачем наследовать от типа в' std', когда переменная-член так же хорошо? – Yakk

+0

@Yakk: Потому что это не так «хорошо». Гораздо проще экспортировать интерфейс из унаследованного класса, чем класс-член. –

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