是否可以实现不受切片影响的 copyable_unique_ptr?

Is it possible to implement a copyable_unique_ptr that is not affected by slicing?

不管复制一个unique_ptr是否有意义*,我尝试实现这种class,简单地包装一个std::unique_ptr,并恰好遇到了困难在获取副本的位置,如果是指向基的智能指针并且存储的对象是派生的 class.

复制构造函数的简单实现可以在互联网上找到(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 的接口)的解决方案不是我认为可以接受的。


*如果您想要一个指向可复制资源的单一拥有指针,这很有意义,它提供的功能远远超过 scoped_ptrauto_ptr 所能提供的。

经过一番努力,让所有的魔法咒语都正确,以便一个好的 C++ 编译器对代码感到满意,并且我对语义感到满意,我向你展示了一个(非常准系统)value_ptr , 具有复制和移动语义。重要的是要记住使用 make_value<Derived> 所以它会选择正确的复制函数,否则副本会切片你的对象。我没有找到实际上具有承受切片机制的 deep_copy_ptrvalue_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 and tests to show how it should work are here 每个人都可以自己看看。随时欢迎评论。