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