shared_ptr magic :)


89

Ми з паном Лідстремом посварились :)

Твердження пана Лідстрема полягає в тому, що конструкція shared_ptr<Base> p(new Derived);не вимагає від Base наявності віртуального деструктора:

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

Даніель Лідстрем : " shared_ptr використовує власний деструктор для видалення конкретного екземпляра. Це відоме як RAII у спільноті C ++. Моя порада полягає в тому, що ви дізнаєтеся все, що можна про RAII. Це значно полегшить ваше кодування на C ++, коли ви використовуєте RAII у всіх ситуаціях ".

Армен Цирунян : "Я знаю про RAII, і я також знаю, що врешті-решт деструктор shared_ptr може видалити збережений px, коли pn досягає 0. Але якщо px мав вказівник статичного типу та вказівник Baseдинамічного типу Derived, то якщо у Baseнього немає віртуального деструктора, це призведе до невизначеної поведінки. Виправте мене, якщо я помиляюся ".

Даніель Лідстрем : " shared_ptr знає, що статичний тип - це Concrete. Він знає це, оскільки я передав його в його конструктор! Це схоже на магію, але я можу запевнити вас, що це за дизайном і надзвичайно приємно".

Отже, судіть нас. Як можна (якщо це можливо) реалізувати shared_ptr, не вимагаючи, щоб поліморфні класи мали віртуальний деструктор? Спасибі заздалегідь


3

8
Ще одна цікава річ - це shared_ptr<void> p(new Derived)також знищить Derivedоб’єкт його деструктором, незалежно від того, є він virtualчи ні.
dalle

7
Чудовий спосіб задати питання :)
rubenvb

5
Незважаючи на те, що shared_ptr це дозволяє, насправді погана ідея розробити клас як базу без віртуального dtor. Коментарі Даніеля щодо RAII вводять в оману - це не має нічого спільного з цим, - але цитована розмова звучить простою помилкою (і невірним припущенням про те, як працює shared_ptr).

6
Не RAII, але він стирає деструктор. Потрібно бути обережним, бо shared_ptr<T>( (T*)new U() )де struct U:Tне буде робити правильно (а це можна зробити побічно легко, наприклад, функція, яка приймає T*і передається а U*)
Якк - Адам Неврамон

Відповіді:


74

Так, можна реалізувати shared_ptr таким чином. Boost робить, і стандарт С ++ 11 також вимагає такої поведінки. Додатковою гнучкістю shared_ptr керує не тільки лічильником посилань. Так званий делетор зазвичай поміщається в той самий блок пам'яті, який також містить лічильники посилань. Але найцікавішим є те, що тип цього видалювача не є частиною типу shared_ptr. Це називається "стиранням типу" і, по суті, є тим самим методом, що використовується для реалізації "поліморфних функцій" boost :: function або std :: функції для приховування фактичного типу функтора. Щоб ваш приклад працював, нам потрібен конструктор із шаблонами:

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. Таким чином, конструктор має можливість створити відповідний об'єкт видалення та лічильники посилань і зберігає вказівник на цей блок управління як елемент даних. Якщо лічильник посилань досягає нуля, для видалення об’єкта буде використано попередньо створений та відомий виведений видалювач

Про цей конструктор (20.7.2.2.1) стандарт С ++ 11 говорить наступне:

Потрібно: 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викликається.

(наголос на жирному шрифті - це моє).


the upcoming standard also requires this behaviour: (a) На який стандарт та (b) ви можете надати посилання (на стандарт)?
kevinarpe

Я просто хочу додати коментар до відповіді @sellibitze, оскільки мені не вистачає балів add a comment. ІМО, це більше Boost does thisніж the Standard requires. Я не думаю, що Стандарт вимагає цього від того, що я розумію. Говорячи про приклад @sellibitze «s shared_ptr<Base> sp (new Derived);, вимагає від constructorпросто попросити delete Derivedбути чітко визначені і добре сформовані. Для специфікації destructorтакож є a p, але я не думаю, що це стосується pспецифікації constructor.
Луджун Венг,

28

Коли 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, щоб знищити загострений об'єкт. Реалізація робить це автоматично.


4
+1 за зауваження про неповні типи, дуже зручне при використанні shared_ptrатрибута.
Matthieu M.

16

Просто,

shared_ptr використовує спеціальну функцію видалення, яка створюється конструктором, який завжди використовує деструктор даного об'єкта, а не деструктор Base, це невелика робота з мета-програмуванням шаблону, але вона працює.

Щось таке

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

1
хм ... цікаво, я починаю вірити в це :)
Армен Цирунян

1
@Armen Tsirunyan Вам слід було заглянути в опис дизайну shared_ptr перед початком обговорення. Це "захоплення видалювача" є однією з найважливіших особливостей shared_ptr ...
Paul Michalik

6
@ paul_71: Я з вами згоден. З іншого боку, я вважаю, що ця дискусія була корисною не тільки для мене, але й для інших людей, які не знали цього про shared_ptr. Тож, мабуть, не було великим гріхом починати цю тему в будь-якому випадку :)
Армен Цирунян

3
@Armen Звичайно ні. Швидше, ви добре зробили, вказавши на цю справді дуже важливу особливість shared_ptr <T>, яку часто контролюють навіть досвідчені розробники C ++.
Пол Міхалік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.