shared_ptr магия :) - PullRequest
       15

shared_ptr магия :)

78 голосов
/ 10 октября 2010

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

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

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

Daniel Lidström :" shared_ptr использует свой собственный деструктор для удаленияКонкретный пример. В сообществе C ++ это называется RAII. Я советую вам узнать все, что вы можете о RAII. Это значительно облегчит кодирование на C ++ при использовании RAII во всех ситуациях. "

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

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

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

Ответы [ 3 ]

69 голосов
/ 10 октября 2010

Да, таким способом можно реализовать 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.Таким образом, конструктор имеет возможность создать соответствующий объект удаления и счетчики ссылок и сохраняет указатель на этот блок управления в качестве элемента данных.Если счетчик ссылок достигает нуля, для удаления объекта будет использоваться ранее созданное и осведомленное о Derived средство удаления.

Стандарт 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.

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

27 голосов
/ 10 октября 2010

Когда создается shared_ptr, он сохраняет в себе deleter объект. Этот объект вызывается, когда 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 для уничтожения указанного объекта. Реализация делает это автоматически.

14 голосов
/ 10 октября 2010

Просто

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

Нечто подобное

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}
...