#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 Если у вас есть альтернативное решение проблемы наличия только указателей базового класса и возможность создания копии без нарезки, я весь внимание.