是否可以实现不受切片影响的 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_ptr
或 auto_ptr
所能提供的。
经过一番努力,让所有的魔法咒语都正确,以便一个好的 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 and tests to show how it should work are here 每个人都可以自己看看。随时欢迎评论。
不管复制一个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_ptr
或 auto_ptr
所能提供的。
经过一番努力,让所有的魔法咒语都正确,以便一个好的 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 and tests to show how it should work are here 每个人都可以自己看看。随时欢迎评论。