Правило ноля C++: полиморфное удаление и поведение unique_ptr

В недавнем журнале о перегрузках в разделе " Применение правила нуля" авторы описывают, как мы можем избежать написания правила пяти операторов, поскольку причины их написания:

  1. Управление ресурсами
  2. Полиморфная делеция

И об этом можно позаботиться, используя умные указатели.

Здесь меня особенно интересует вторая часть.

Рассмотрим следующий фрагмент кода:

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 не делает?

3 ответа

Решение

Ваш ответ здесь: /questions/26455254/virtualnyij-destruktor-s-virtualnyimi-chlenami-v-c11/26455266#26455266

Цитата:

После последнего обращения 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 непусто, когда вызывается удалитель).

Другие вопросы по тегам