2016-10-24 14 views
2

Независимо от того, что копирование unique_ptr имеет смысл или нет *, я попытался реализовать этот класс, просто обернув std::unique_ptr и столкнувшись с трудностями, где именно копируется, в случае умного указателя на базу и сохраненный объект является производным классом.Возможно ли реализовать copyable_unique_ptr, на который не повлияет нарезка?

Наивная реализация конструктора копии можно найти по всему Интернету (data является завернутым std::unique_ptr):

copyable_unique_ptr::copyable_unique_ptr(const copyable_unique_ptr& other) 
    : data(std::make_unique(*other.get()) // invoke the class's copy constructor 
{} 

Проблемы здесь в том, что из-за аргументы, оставшегося из шаблона, является то, что копия создает экземпляр типа T, даже если реальный тип U : T. Это приводит к потере информации о копии, и, хотя я прекрасно понимаю, почему это происходит здесь, я не могу найти способ обойти это.

Обратите внимание, что в случае перемещения нет проблем. Исходный указатель был правильно создан где-то в пользовательском коде, и перемещение его к новому владельцу не изменяет реальный тип объекта. Чтобы сделать копию, вам нужна дополнительная информация.

Также обратите внимание, что решение, использующее функцию clone (таким образом, заражая интерфейс типа T), не является тем, что я считаю приемлемым.


* если вы хотите, один указатель, владеющего к Copyable ресурса, это может иметь смысл, и это дает гораздо больше, чем то, что scoped_ptr или auto_ptr обеспечит.

+0

вот так? https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern#Polymorphic_copy_construction – Hayt

+0

@Hayt: прочитайте мое последнее предложение выше строки. – rubenvb

+0

Я имею в виду, что вы вводите новый тип между 'T' и' U'. Таким образом, не заражать Т напрямую. – Hayt

ответ

1

После некоторых попыток получить все магические заклинания вправо, чтобы хороший компилятор C++ был удовлетворен кодом, и я был доволен семантикой, я представляю вам (очень баребоны) value_ptr с копиями и переместить семантику. Важно помнить, что нужно использовать make_value<Derived>, чтобы получить правильную функцию копирования, иначе копия будет срезать ваш объект. Я не нашел реализацию deep_copy_ptr или value_ptr, у которой фактически был механизм, способный выдержать нарезку. Это грубая обрезом реализация, которая пропускает такие вещи, как мелкозернистой обработки справочной или специализации массива, но здесь, тем не менее:

template <typename T> 
static void* (*copy_constructor_copier())(void*) 
{ 
    return [](void* other) 
     { return static_cast<void*>(new T(*static_cast<T*>(other))); }; 
} 

template<typename T> 
class smart_copy 
{ 
public: 
    using copy_function_type = void*(*)(void*); 

    explicit smart_copy() { static_assert(!std::is_abstract<T>::value, "Cannot default construct smart_copy for an abstract type."); } 
    explicit smart_copy(copy_function_type copy_function) : copy_function(copy_function) {} 
    smart_copy(const smart_copy& other) : copy_function(other.get_copy_function()) {} 
    template<typename U> 
    smart_copy(const smart_copy<U>& other) : copy_function(other.get_copy_function()) {} 

    void* operator()(void* other) const { return copy_function(other); } 
    copy_function_type get_copy_function() const { return copy_function; } 

private: 
    copy_function_type copy_function = copy_constructor_copier<T>(); 
}; 

template<typename T, 
     typename Copier = smart_copy<T>, 
     typename Deleter = std::default_delete<T>> 
class value_ptr 
{ 
    using pointer = std::add_pointer_t<T>; 
    using element_type = std::remove_reference_t<T>; 
    using reference = std::add_lvalue_reference_t<element_type>; 
    using const_reference = std::add_const_t<reference>; 
    using copier_type = Copier; 
    using deleter_type = Deleter; 

public: 
    explicit constexpr value_ptr() = default; 
    explicit constexpr value_ptr(std::nullptr_t) : value_ptr() {} 
    explicit value_ptr(pointer p) : data{p, copier_type(), deleter_type()} {} 

    ~value_ptr() 
    { 
    reset(nullptr); 
    } 

    explicit value_ptr(const value_ptr& other) 
    : data{static_cast<pointer>(other.get_copier()(other.get())), other.get_copier(), other.get_deleter()} {} 
    explicit value_ptr(value_ptr&& other) 
    : data{other.get(), other.get_copier(), other.get_deleter()} { other.release(); } 
    template<typename U, typename OtherCopier> 
    value_ptr(const value_ptr<U, OtherCopier>& other) 
    : data{static_cast<pointer>(other.get_copier().get_copy_function()(other.get())), other.get_copier(), other.get_deleter()} {} 
    template<typename U, typename OtherCopier> 
    value_ptr(value_ptr<U, OtherCopier>&& other) 
    : data{other.get(), other.get_copier(), other.get_deleter()} { other.release(); } 

    const value_ptr& operator=(value_ptr other) { swap(data, other.data); return *this; } 
    template<typename U, typename OtherCopier, typename OtherDeleter> 
    value_ptr& operator=(value_ptr<U, OtherCopier, OtherDeleter> other) { std::swap(data, other.data); return *this; } 

    pointer operator->() { return get(); } 
    const pointer operator->() const { return get(); } 

    reference operator*() { return *get(); } 
    const_reference operator*() const { return *get(); } 

    pointer get() { return std::get<0>(data); } 
    const pointer get() const { return std::get<0>(data); } 

    copier_type& get_copier() { return std::get<1>(data); } 
    const copier_type& get_copier() const { return std::get<1>(data); } 
    deleter_type& get_deleter() { return std::get<2>(data); } 
    const deleter_type& get_deleter() const { return std::get<2>(data); } 

    void reset(pointer new_data) 
    { 
    if(get()) 
    { 
     get_deleter()(get()); 
    } 
    std::get<0>(data) = new_data; 
    } 

    pointer release() noexcept 
    { 
    pointer result = get(); 
    std::get<0>(data) = pointer(); 
    return result; 
    } 

private: 
    std::tuple<pointer, copier_type, deleter_type> data = {nullptr, smart_copy<T>(), std::default_delete<T>()}; 
}; 

template<typename T, typename... ArgTypes> 
value_ptr<T> make_value(ArgTypes&&... args) 
{ 
    return value_ptr<T>(new T(std::forward<ArgTypes>(args)...));; 
} 

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

+0

Защита от обрезки просто не стоит накладных расходов, которые этот класс накладывает на каждый экземпляр 'value_ptr'. –

+0

@Nicol Если у вас есть альтернативное решение проблемы наличия указателей только базового класса и возможности сделать нерезкую копию, я все уши. – rubenvb