В последнее время журнал перегрузки по теме Обеспечение соблюдения правила нуля, авторы описывают, как мы можем избежать написания правила пяти операторов, поскольку причины их написания:
И об этом можно позаботиться, используя умные указатели.
Здесь меня особенно интересует вторая часть.
Рассмотрим следующий фрагмент кода:
class Base
{
public:
virtual void Fun() = 0;
};class Derived : public Base
{
public:
~Derived()
{
cout << "Derived::~Derived\n";
}
void Fun()
{
cout << "Derived::Fun\n";
}
};int main()
{
shared_ptr<Base> pB = make_shared<Derived>();
pB->Fun();
}
В этом случае, как объясняют авторы статьи, мы получаем полиморфное удаление с помощью общего указателя, и это работает.
Но если я заменю shared_ptr
с unique_ptr
Я больше не могу наблюдать полиморфное удаление.
Теперь мой вопрос: почему эти два поведения отличаются? Почему shared_ptr
заботиться о полиморфном удалении в то время как unique_ptr
не делает?
У вас есть ответ здесь: https://stackoverflow.com/a/22861890/2007142
Цитата:
После последнего обращения
shared_ptr
выходит из области видимости или сбрасывается,~Derived()
будет вызван и память освобождена. Поэтому вам не нужно делать~Base()
виртуальная.unique_ptr<Base>
а такжеmake_unique<Derived>
не предоставляют эту функцию, потому что они не обеспечивают механикуshared_ptr
по отношению к удалителю, поскольку уникальный указатель намного проще и нацелен на минимальные издержки и, следовательно, не хранит указатель дополнительной функции, необходимый для удалителя.
Это будет работать, если вы используете C ++ 14 make_unique
или напишите свой, как в ответе Якка. По сути, разница между поведением общего указателя в том, что вы получили:
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
за unique_pointer
и, как вы можете видеть, удалитель относится к типу. Если вы объявите unique_pointer<Base>
это всегда будет использовать std::default_delete<Base>
по умолчанию. Но make_unique
позаботится об использовании правильного удалителя для вашего класса.
Когда используешь shared_ptr
ты получил:
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
и другие перегрузки в качестве конструктора. Как вы можете видеть по умолчанию для удаления unique_ptr
зависит от параметра шаблона при объявлении типа (если вы не используете make_unique
в то время как для shared_ptr
средство удаления зависит от типа, переданного конструктору.
Вы можете увидеть версию, которая позволяет полиморфное удаление без виртуального деструктора Вот (эта версия также должна работать в VS2012). Обратите внимание, что это довольно много взломано вместе, и я в настоящее время не уверен, что поведение unique_ptr
а также make_shared
в C ++ 14 будет похоже, но я надеюсь, что они сделают это проще. Может быть, я загляну в статьи для дополнений C ++ 14 и посмотрю, изменилось ли что-нибудь, если бы я нашел время позже.
template<typename T>
using smart_unique_ptr=std::unique_ptr<T,void(*)(void*)>;
template<class T, class...Args> smart_unique_ptr<T> make_smart_unique(Args&&...args) {
return {new T(std::forward<Args>(args)...), [](void*t){delete (T*)t;}};
}
Проблема в том, что по умолчанию для удаления unique_ptr
звонки delete
на сохраненном указателе. Выше хранит средство удаления, которое знает тип при построении, поэтому при копировании в базовый класс unique_ptr
все равно удаляю как ребенка.
Это добавляет скромные накладные расходы, поскольку мы должны разыменовать указатель. Кроме того, он денормализует тип по умолчанию smart_unique_ptr
теперь незаконно. Вы можете исправить это с помощью некоторой дополнительной работы (замените необработанный указатель на функцию полуумным функтором, который по крайней мере не падает; указатель на функцию, однако, следует утверждать, что он существует, если unique
непусто, когда вызывается удалитель).