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

Regardless of whether copying a unique_ptr makes sense or not *, I tried to implement this class by simply wrapping std::unique_ptr and running into difficulties when the copy was taken, in the case of a smart pointer to the base and the stored object, which is a derived class.

A naive copy constructor implementation can be found all over the Internet ( data - wrapped std::unique_ptr ):

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

The problem here is that because of the left out template arguments, the copy creates an instance of type T , even if the actual type is U : T This leads to a loss of copy information, and although I understand perfectly why this is happening here, I cannot find a way around this.

Please note that there is no problem with moving. The original pointer was correctly created somewhere in the user code, and transferring it to the new owner does not change the real type of the object. To make a copy, you need more information.

Also note that a solution using the clone function (thus infecting an interface like T ) is not what I consider acceptable.


* if you need one pointer to the resource to be copied, which may make sense, and it provides much more than scoped_ptr or auto_ptr provides.

+2
source share
1 answer

After some attempts to get all the magic spells correctly, so that a good C ++ compiler is satisfied with the code, and I was happy with the semantics, I present to you (very barebones) value_ptr , with both instances and move the semantics. It is important to remember that you should use make_value<Derived> so that it selects the correct copy function, otherwise the copy will cut your object. I did not find the deep_copy_ptr or value_ptr implementation, which actually had a mechanism to withstand slicing. This is a complex implementation that skips things like fine-grained link processing or specializing in arrays, but here it is nonetheless:

 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)...));; } 

The code lives here and tests to show how it should work, here so that everyone can see for themselves. Comments are always welcome.

+1
source

Source: https://habr.com/ru/post/1258903/


All Articles