Пол: Мужской

Г-н. У Лидстрема и меня был аргумент:)

г. Требование 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.