Пол: Мужской
Г-н. У Лидстрема и меня был аргумент:)
г. Требование Lidström состоит в том, что конструктор shared_ptr<Base> p(new Derived);
не требует, чтобы Base имел виртуальный деструктор:
Армен Цирунян: "Действительно? Будет ли очистка shared_ptr правильно? Не могли бы вы в этом случае продемонстрировать, как этот эффект может быть реализован?"
Daniel Lidström: "shared_ptr использует свой собственный деструктор для удаления экземпляра Concrete. Это называется RAII в сообществе С++. Мой совет заключается в том, что вы узнаете все о RAII. сделайте ваше С++-кодирование намного проще, когда вы используете RAII во всех ситуациях".
Армен Цирунян: "Я знаю об RAII, и я также знаю, что в конечном итоге деструктор shared_ptr может удалить сохраненные px, когда pn достигнет 0. Но если px имеет указатель статического типа на Base
и динамический указатель типа Derived
, тогда, если Base
не имеет виртуального деструктора, это приведет к поведению undefined. Исправьте меня, если я ошибаюсь."
Daniel Lidström: "shared_ptr знает, что статический тип - это Concrete. Он знает это, потому что я передал его в своем конструкторе! Кажется, это немного похоже на магию, но я могу заверить вас, что это по дизайну и очень приятно".
Итак, судите нас. Как возможно (если это так) реализовать shared_ptr, не требуя, чтобы полиморфные классы имели виртуальный деструктор?
Спасибо заранее
Ответы
Ответ 1
Да, таким образом можно реализовать shared_ptr. Boost делает, и стандарт С++ 11 также требует такого поведения. В качестве дополнительной гибкости shared_ptr управляет не только контрольным счетчиком. Так называемый deleter обычно помещается в тот же блок памяти, который также содержит контрольные счетчики. Но забавная часть заключается в том, что тип этого делетера не является частью типа 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. Таким образом, у конструктора есть возможность создать соответствующий объект-делетер и ссылочные счетчики и сохраняет указатель на этот блок управления в качестве элемента данных. Если контрольный счетчик достигнет нуля, для удаления объекта будет использоваться ранее созданный и деривативный код.
В стандарте С++ 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
и вызывается deleter d
, d(p)
. В противном случае, если *this
владеет указателем p
и вызывается delete p
.
(выделение жирным шрифтом - мое).
Ответ 2
Когда 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 для уничтожения заостренного объекта. Реализация делает это автоматически.
Ответ 3
Просто,
shared_ptr
использует специальную функцию делетера, которая создается конструктором, который всегда использует
деструктор данного объекта, а не деструктор базы, это немного работа с метапрограммой шаблона, но он работает.
Что-то вроде этого
template<typename SomeType>
shared_ptr(SomeType *p)
{
this->destroyer = destroyer_function<SomeType>(p);
...
}
Ответ 4
Я просто хочу добавить комментарий к ответу @sellibitze, так как у меня недостаточно очков, чтобы add a comment
.
ИМО, это больше Boost does this
чем the Standard requires
. Я не думаю, что Стандарт требует этого из того, что я понимаю. Говоря о @sellibitze, пример shared_ptr<Base> sp (new Derived);
, Требуется, чтобы constructor
просто попросил delete Derived
будучи хорошо определенным и правильно сформированным. Для спецификации destructor
также существует p
, но я не думаю, что это относится к p
в спецификации constructor
.