Чи зберігається делетер shared_ptr в пам'яті, виділений спеціальним розподільником?


22

Скажіть, у мене є shared_ptrспеціальний розподільник і користувацький делетер.

Я не можу знайти нічого, що відповідає стандарту, який би говорив про те, де слід зберігати делетер: він не говорить про те, що користувацький розподільник буде використовуватися для пам'яті делетера, і не говорить про те, що його не буде.

Це не визначено чи я просто щось пропускаю?

Відповіді:


11

util.smartptr.shared.const / 9 в C ++ 11:

Ефекти: Конструює об'єкт shared_ptr, який належить об'єкту p та делетору d. Другий і четвертий конструктори повинні використовувати копію а, щоб виділити пам'ять для внутрішнього використання.

Другий і четвертий конструктори мають такі прототипи:

template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);

В останньому проекті util.smartptr.shared.const / 10 еквівалентно нашій меті:

Ефекти: Конструює об'єкт shared_ptr, який належить об'єкту p та делетору d. Якщо T не тип масиву, перший і другий конструктори включають спільний_з_ це з p. Другий і четвертий конструктори повинні використовувати копію а, щоб виділити пам'ять для внутрішнього використання. Якщо викинутий виняток, називається d (p).

Отже, розподільник використовується, якщо є необхідність виділити його у виділеній пам'яті. Виходячи з діючого стандарту та відповідних звітів про дефекти, розподіл не є обов'язковим, але передбачається комітетом.

  • Хоча інтерфейс shared_ptrдозволяє реалізовувати там, де ніколи не існує керуючого блоку, а всі shared_ptrі weak_ptrмістяться у зв'язаному списку, на практиці такої реалізації немає. Крім того, формулювання було змінено, передбачаючи, наприклад, що use_countспільне використання.

  • Делетер зобов'язаний рухатись лише конструктивними. Таким чином, неможливо мати кілька примірників у shared_ptr.

Можна уявити реалізацію, яка ставить делетер у спеціально розроблений shared_ptrта переміщує його, коли shared_ptrвидаляється спеціальний . Хоча реалізація здається сумісною, вона також дивна, тим більше, що для підрахунку використання може знадобитися блок управління (можливо, але навіть більш дивно зробити те ж саме з кількістю використання).

Відповідні DR-файли, які я знайшов: 545 , 575 , 2434 (які підтверджують, що всі реалізації використовують блок управління і, здається, передбачають, що обмеження багатопотокових обмежень дещо наказує це), 2802 (що вимагає, щоб делетер рухався лише конструктивно, і таким чином перешкоджає виконанню, де делетер копіюється між декількома shared_ptr's).


2
"виділити пам'ять для внутрішнього використання" Що робити, якщо реалізація не буде виділяти пам'ять для внутрішнього використання для початку? Це може використовувати член.
ЛФ

1
@LF Не може, інтерфейс цього не дозволяє.
AProgrammer

Теоретично вона все ще може використовувати якусь "малу оптимізацію делетерів", правда?
ЛФ

Що дивно, що я не можу знайти нічого про використання же аллокатора (копія a) , щоб звільнити цю пам'ять. Що означало б деяке зберігання цієї копії a. Інформації про це немає в [util.smartptr.shared.dest].
Даніель

1
@DanielsaysreinstateMonica, мені цікаво, чи в util.smartptr.shared / 1: "Шаблон класу shared_ptr зберігає вказівник, як правило, отриманий за допомогою нового. Shared_ptr реалізує семантику спільної власності; останній власник вказівника відповідає за знищення об'єкта, або іншим чином звільнити ресурси, пов'язані із збереженим покажчиком. " то звільняють ресурси , пов'язані зі збереженим покажчиком не призначені для цього. Але блок управління повинен також зберегтись, поки не буде видалений останній слабкий покажчик.
AProgrammer

4

З std :: shared_ptr у нас є:

Блок управління - це динамічно виділений об'єкт, який містить:

  • або вказівник на керований об’єкт, або на сам керований об'єкт;
  • делетер (стираний тип);
  • розподільник (стираний тип);
  • кількість shared_ptrs, які володіють керованим об'єктом;
  • кількість слабких_птрів, які посилаються на керований об'єкт.

І з std :: allocate_shared ми отримуємо:

template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );

Конструює об’єкт типу T і загортає його в std :: shared_ptr [...], щоб використовувати одне розподілення як для блоку управління спільного вказівника, так і для T об’єкта.

Так виглядає, що std :: allocate_shared повинен виділити свій deleterз вашим Alloc.

EDIT: І з n4810§20.11.3.6 Створення [util.smartptr.shared.create]

1 Загальні вимоги , які застосовуються до всіх make_shared, allocate_shared, make_shared_default_init, і allocate_shared_default_initперевантаженнях, якщо не вказано інше, описані нижче.

[...]

7 Зауваження: (7.1) - Реалізація повинна виконувати не більше одного розподілу пам'яті. [Примітка. Це забезпечує ефективність, еквівалентну нав'язливому інтелектуальному покажчику. —Закінчити примітку]

[Наголос все моє]

Так стандарт говорить, що std::allocate_shared слід використовувати Allocдля блоку управління.


1
Вибачте, cppreference не є нормативним текстом. Це чудовий ресурс, але не обов’язково для мовно-юристських питань.
StoryTeller - Невідповідна Моніка

@ StoryTeller-UnslanderMonica Цілком погоджуюся - переглянув найновіший стандарт і нічого не вдалося знайти, тому йшло з cppreference.
Пол Еванс


Знайдена n4810та оновлена ​​відповідь.
Пол Еванс

1
Однак це йдеться make_sharedне про самих конструкторів. Все-таки я можу використовувати член для невеликих делетерів.
LF

3

Я вважаю, що це не визначено.

Ось специфікація відповідних конструкторів: [util.smartptr.shared.const] / 10

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

Ефекти: Конструює shared_­ptrоб’єкт, який належить об'єкту pта делетору d. Коли Tце не тип масиву, перший та другий конструктори включають за shared_­from_­thisдопомогою p. Другий і четвертий конструктори повинні використовувати копію, aщоб виділити пам'ять для внутрішнього використання . Якщо викинутий виняток, d(p)викликається.

Тепер моє тлумачення полягає в тому, що коли для реалізації потрібна пам'ять для внутрішнього використання, вона робить це за допомогою a. Це не означає, що реалізація повинна використовувати цю пам'ять, щоб розмістити все. Наприклад, припустимо, що існує ця дивна реалізація:

template <typename T>
class shared_ptr : /* ... */ {
    // ...
    std::aligned_storage<16> _Small_deleter;
    // ...
public:
    // ...
    template <class _D, class _A>
    shared_ptr(nullptr_t, _D __d, _A __a) // for example
        : _Allocator_base{__a}
    {
        if constexpr (sizeof(_D) <= 16)
            _Construct_at(&_Small_deleter, std::move(__d));
        else
            // use 'a' to allocate storage for the deleter
    }
// ...
};

Чи реалізується ця реалізація "копією aдля розподілу пам'яті для внутрішнього використання"? Так. Він ніколи не виділяє пам'ять, крім використання a. Існує багато проблем з цією наївною реалізацією, але скажімо, що вона переходить на використання алокаторів у всіх, крім найпростішого випадку, коли shared_ptrконструкція побудована безпосередньо з вказівника і ніколи не копіюється, не переміщується або посилається іншим чином і немає ніяких інших ускладнень. Справа в тому, що ми не можемо уявити, що дійсна реалізація сама по собі не доводить, що вона теоретично не може існувати. Я не кажу, що таке реалізація насправді можна знайти в реальному світі, лише те, що стандарт, здається, не забороняє цього.


IMO ваш shared_ptrдля малих типів виділяє пам'ять на стек. І так не відповідає стандартним вимогам
bartop

1
@bartop Це не "виділяє" пам'ять на стеку. _Smaller_deleter безумовно є частиною представлення спільного_ptr. Викликати конструктор у цьому просторі не означає виділяти що-небудь. В іншому випадку навіть указівник на блок управління вважається "виділенням пам'яті", правда? :-)
LF

Але видалювач не повинен бути копіюваним, так як би це працювало?
Ніколь

@ NicolBolas Umm ... Використовуйте std::move(__d)та повертайтесь до allocateпотрібної копії.
LF
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.