Возможно ли реализовать copyable_unique_ptr, на который не влияет нарезка?

#c 11 #copy-constructor #object-slicing

#c 11 #copy-constructor #объектная нарезка

Вопрос:

Независимо от того, имеет ли смысл копировать unique_ptr или нет *, я попытался реализовать класс такого типа, просто обернув std::unique_ptr , и столкнулся с трудностями, когда именно берется копия, в случае интеллектуального указателя на base и сохраненного объекта, являющегося производным классом.

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

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

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

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

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


* если вам нужен единственный указатель-владелец на копируемый ресурс, это может иметь смысл, и это обеспечивает гораздо больше, чем то, что предоставил бы scoped_ptr or auto_ptr .

Комментарии:

1. вот так? en.wikipedia.org/wiki /…

2. @Hayt: пожалуйста, прочтите мое последнее предложение над строкой.

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

4. Будут ли для вас проблемой функции, не являющиеся членами, и дублирование кода? (Хотя функции, не являющиеся членами, также можно рассматривать как часть интерфейса T) Если вы этого не хотите, я не думаю, что найдется хорошее решение. Я знаю об уродливом варианте, хотя, если вы можете отредактировать конструктор копирования T (с еще большим дублированием кода)

5. Хм, кажется, я нашел то, что искал, по-видимому, называется value_ptr . Пример реализации здесь: bitbucket.org/martinhofernandes/wheels/src /… .

Ответ №1:

После некоторых усилий с исправлением всех магических заклинаний, чтобы хороший компилятор C был удовлетворен кодом, а я был удовлетворен семантикой, я представляю вам (очень простой) value_ptr вариант с семантикой копирования и перемещения. Важно помнить, что нужно использовать make_value<Derived> так, чтобы он подбирал правильную функцию копирования, иначе копия будет нарезать ваш объект. Я не нашел реализации deep_copy_ptr or 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_copyamp; other) : copy_function(other.get_copy_function()) {}
  template<typename U>
  smart_copy(const smart_copy<U>amp; 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_ptramp; other)
    : data{static_cast<pointer>(other.get_copier()(other.get())), other.get_copier(), other.get_deleter()} {}
  explicit value_ptr(value_ptramp;amp; other)
    : data{other.get(), other.get_copier(), other.get_deleter()} { other.release(); }
  template<typename U, typename OtherCopier>
  value_ptr(const value_ptr<U, OtherCopier>amp; 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>amp;amp; other)
    : data{other.get(), other.get_copier(), other.get_deleter()} { other.release(); }

  const value_ptramp; operator=(value_ptr other) { swap(data, other.data); return *this; }
  template<typename U, typename OtherCopier, typename OtherDeleter>
  value_ptramp; 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_typeamp; get_copier() { return std::get<1>(data); }
  const copier_typeamp; get_copier() const { return std::get<1>(data); }
  deleter_typeamp; get_deleter() { return std::get<2>(data); }
  const deleter_typeamp; 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 resu<
  }

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(ArgTypesamp;amp;... args)
{
  return value_ptr<T>(new T(std::forward<ArgTypes>(args)...));;
}
  

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

Комментарии:

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

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