shared_ptr магия:)

Мистер Лидстрём и я поссорились:)

Г-н Лидстрём утверждает, что конструкция shared_ptr<Base> p(new Derived); не требует, чтобы у Base был виртуальный деструктор:

Армен Цирунян: "Действительно? Будет ли корректно очищен shared_ptr? Не могли бы вы в этом случае продемонстрировать, как этот эффект может быть реализован?"

Даниэль Лидстрем: " shared_ptr использует свой собственный деструктор для удаления экземпляра Concrete. В сообществе C++ это называется RAII. Мой совет - вы узнаете все, что можете о RAII. Это значительно облегчит кодирование на C++ при использовании. RAII во всех ситуациях."

Армен Цирунян: "Я знаю о RAII, и я также знаю, что в конечном итоге деструктор shared_ptr может удалить сохраненный px, когда pn достигнет 0. Но если у px был указатель статического типа на Base и указатель динамического типа на Derived тогда Base имеет виртуальный деструктор, это приведет к неопределенному поведению. Поправь меня, если я ошибаюсь ".

Даниэль Лидстрем: " shared_ptr знает, что статический тип - это Concrete. Он знает об этом с тех пор, как я передал его в его конструктор! Это похоже на магию, но я могу заверить вас, что оно разработано и очень красиво".

Итак, судите нас. Как можно (если это так) реализовать shared_ptr, не требуя, чтобы полиморфные классы имели виртуальный деструктор? заранее спасибо

3 ответа

Решение

Да, можно реализовать shared_ptr таким образом. Boost делает, и стандарт C++11 также требует такого поведения. В качестве дополнительной гибкости shared_ptr управляет не только счетчиком ссылок. Так называемое средство удаления обычно помещается в тот же блок памяти, который также содержит счетчики ссылок. Но самое интересное в том, что тип этого удалителя не является частью типа shared_ptr. Это называется "стиранием типа" и в основном является тем же методом, который используется для реализации "полиморфных функций" boost::function или std::function для сокрытия фактического типа функтора. Чтобы ваш пример работал, нам нужен шаблонный конструктор:

template<class T>
class shared_ptr
{
public:
   ...
   template<class Y>
   explicit shared_ptr(Y* p);
   ...
};

Итак, если вы используете это с вашими классами Base и Derived...

class Base {};
class Derived : public Base {};

int main() {
   shared_ptr<Base> sp (new Derived);
}

... шаблонный конструктор с Y=Derived используется для создания объекта shared_ptr. Таким образом, конструктор имеет возможность создать соответствующий объект удаления и счетчики ссылок и сохранить указатель на этот блок управления в качестве члена данных. Если счетчик ссылок достигает нуля, для удаления объекта будет использоваться ранее созданное и осведомленное о производных средство удаления.

Стандарт C++11 может сказать следующее об этом конструкторе (20.7.2.2.1):

Требуется: p должен быть конвертируемым в T*, Y должен быть полным типом. Выражение delete p должны быть правильно сформированы, иметь четко определенное поведение и не создавать исключений.

Эффекты: Создает shared_ptr объект, которому принадлежит указатель p ,

...

А для деструктора (20.7.2.2.2):

Эффекты: если *this пусто или делится собственностью с другим shared_ptr пример (use_count() > 1), побочных эффектов нет. В противном случае, если *this владеет объектом p и удалитель d, d(p) называется. В противном случае, если *this владеет указателем p , а также delete p называется.

(выделение жирным шрифтом мое).

Когда shared_ptr создан, он хранит объект удаления внутри себя. Этот объект вызывается, когда shared_ptr собирается освободить указанный ресурс. Поскольку вы знаете, как уничтожить ресурс на этапе создания, вы можете использовать shared_ptr с неполными типами. Кто бы ни создал shared_ptr, он сохранил там правильный удалитель.

Например, вы можете создать собственный удалитель:

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed.

shared_ptr<Base> p(new Derived, DeleteDerived);

p вызовет DeleteDerived, чтобы уничтожить указанный объект. Реализация делает это автоматически.

Просто,

shared_ptr использует специальную функцию удаления, которая создается конструктором, который всегда использует деструктор данного объекта, а не деструктор Base, это немного работает с шаблонным метапрограммированием, но это работает.

Что-то вроде того

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}
Другие вопросы по тегам