【问题标题】:Is it possible to implement a copyable_unique_ptr that is not affected by slicing?是否可以实现不受切片影响的 copyable_unique_ptr ?
【发布时间】:2017-03-06 02:15:54
【问题描述】:

不管复制unique_ptr 是否有意义*,我都尝试实现这种类,只是简单地包装了std::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
{}

这里的问题是,由于遗漏了模板参数,即使实际类型是U : T,副本也会创建T 类型的实例。这会导致副本上的信息丢失,虽然我完全理解为什么会发生这种情况,但我找不到解决方法。

注意,在移动的情况下,没有问题。原始指针是在用户代码中的某处正确创建的,将其移动到新所有者不会修改对象的实际类型。要制作副本,您需要更多信息。

另请注意,使用clone 函数的解决方案(从而感染类型T 的接口)不是我认为可以接受的。


*如果您想要一个指向可复制资源的单一拥有指针,这是有道理的,它提供的功能比scoped_ptrauto_ptr 提供的要多得多。

【问题讨论】:

  • @Hayt:请阅读我的最后一句话。
  • 我的意思是你会在TU之间引入一个新类型。因此不会直接感染 T。
  • 非成员函数和代码重复对您来说是个问题吗? (非成员函数也可以看作是 T 接口的一部分)如果你不想这样,我认为不会有一个好的解决方案。我知道一个丑陋的,但如果你可以编辑 T 的复制构造函数(甚至更多的代码重复)
  • 嗯,看来我找到了我要找的东西,显然叫做value_ptr。此处的示例实现:bitbucket.org/martinhofernandes/wheels/src/….

标签: c++11 copy-constructor object-slicing


【解决方案1】:

在努力让所有的魔法咒语都正确之后,让一个好的 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 和测试以显示它应该如何工作是here 供每个人自己查看。随时欢迎评论。

【讨论】:

  • 切片保护根本不值得这个类对每个 value_ptr 实例施加的开销。
  • @Nicol 如果你有一个替代解决方案来解决只有基类指针的问题并且可以制作非切片副本,我会全力以赴。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-05
  • 1970-01-01
  • 2017-05-07
  • 1970-01-01
  • 1970-01-01
  • 2019-09-29
  • 1970-01-01
相关资源
最近更新 更多