Який тип покажчика я використовую, коли?


228

Гаразд, тож востаннє, коли я писав C ++ на життя, std::auto_ptrбув у всіх наявних std ліб, і він boost::shared_ptrбув весь гнів. Я ніколи насправді не роздивлявся, якщо передбачено збільшення інших типів розумних вказівників. Я розумію, що C ++ 11 надає деякі типи прискорених типів, але не всі.

Так у когось є простий алгоритм, щоб визначити, коли використовувати який розумний покажчик? Переважно, включаючи поради щодо німих покажчиків (подібних необроблених вказівників T*) та решти прискорених інтелектуальних вказівників. (Що - щось на зразок цього було б здорово).



1
Я дуже сподіваюся, що хтось придумає приємну зручну блок-схему, як ця блок-схема вибору STL .
Alok Зберегти

1
@Als: О, це справді приємно! Я задав це питання.
sbi

6
@Deduplicator Це навіть не є дублікатом. Пов'язаний питання каже : «Коли я повинен використовувати в смарт - покажчик» і це питання «Коли я використовую ці розумні покажчики?» тобто це категоризація різних видів використання стандартних інтелектуальних покажчиків. Зв'язане питання цього не робить. Різниця, здавалося б, мала, але велика.
Rapptz

Відповіді:


183

Загальна власність: і стандарт прийнятий в значній мірі так само , як їх буста аналоги . Використовуйте їх, коли вам потрібно поділитися ресурсом, і не знаєте, який з них буде останнім. Використовуйте для спостереження за спільним ресурсом, не впливаючи на його термін експлуатації, не порушуючи цикли. Цикли із звичайним не повинні відбуватися - два ресурси не можуть володіти один одним.
shared_ptrweak_ptrweak_ptrshared_ptr

Зауважте, що Boost додатково пропонує shared_array, що може бути підходящою альтернативою shared_ptr<std::vector<T> const>.

Далі, Boost пропонує intrusive_ptrполегшене рішення, якщо ваш ресурс вже пропонує довідкову систему управління і ви хочете прийняти його за принципом RAII. Цей стандарт не був прийнятий.

Унікальне право власності:
Boost також має a scoped_ptr, який не підлягає копіюванню і для якого ви не можете вказати делетер. std::unique_ptrзнаходиться boost::scoped_ptrна стероїдах і повинен стати вашим вибором за замовчуванням, коли вам потрібен розумний вказівник . Це дозволяє вказати делетер у аргументах шаблону і є рухомим , на відміну від цього boost::scoped_ptr. Він також повністю використовується в контейнерах STL до тих пір, поки ви не використовуєте операції, для яких потрібні типи, що можна скопіювати (очевидно).

Зауважимо ще раз, що Boost має версію масиву:, scoped_arrayщо стандарт уніфікований, вимагаючи std::unique_ptr<T[]>часткової спеціалізації, яка буде delete[]вказувати замість deleteing (з default_deleter). std::unique_ptr<T[]>також пропонує operator[]замість operator*і operator->.

Зауважте, що std::auto_ptrце все ще є у стандарті, але воно застаріле . §D.10 [depr.auto.ptr]

Шаблон класу auto_ptrзастарілий. [ Примітка: Шаблон класу unique_ptr(20.7.1) забезпечує краще рішення. —Закінчити примітку ]

Немає права власності:
Використовуйте німі вказівники (необроблені вказівники) або посилання для невласників посилань на ресурси, і коли ви знаєте, що ресурс переживе референтний об'єкт / область. Віддайте перевагу посиланням і використовуйте необроблені покажчики, коли вам потрібна ні зведеність, ні перестановка.

Якщо ви хочете невласнити посиланням на ресурс, але ви не знаєте, чи ресурс пережив об’єкт, на який посилається, запакуйте ресурс в a shared_ptrі використовуйте a weak_ptr- ви можете перевірити, чи shared_ptrне живе батько lock, який буде повернути a, shared_ptrщо не має значення, якщо ресурс все ще існує. Якщо ви хочете перевірити, чи ресурс мертвий, використовуйте expired. Це може здатися схожим, але сильно відрізняється за умови одночасного виконання, оскільки expiredгарантує лише його повернене значення для цього окремого висловлювання. Начебто невинний тест на кшталт

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

є потенційною умовою перегонів.


1
У випадку відсутності права власності ви, мабуть, віддасте перевагу посиланням на покажчики, якщо вам не потрібні права власності та перезавантаження, якщо посилання не вирізатимуть її, навіть тоді ви можете розглянути можливість переписування оригінального об'єкта а, shared_ptrа невласним покажчиком бути a weak_ptr...
Девід Родрігес - дрибес

2
Я мав на увазі не посилання на покажчик , а скоріше посилання замість вказівника. Якщо права власності відсутня, якщо вам не потрібна перестановка (або зведення нанівець, але зведеність на зразок без можливості скидання була б досить обмеженою), ви можете використовувати просту посилання, а не вказівник в першу чергу.
Девід Родрігес - дрибес

1
@David: Ах, бачу. :) Так, посилання на це непогані, я особисто їм віддаю перевагу в таких випадках. Я додам їх.
Ксео

1
@Xeo: shared_array<T>це альтернатива shared_ptr<T[]>не робити shared_ptr<vector<T>>: вона не може рости.
Р. Мартіньо Фернандес

1
@GregroyCurrie: Це ... саме те, що я написав? Я сказав, що це приклад потенційного стану гонки.
Xeo

127

Вирішення того, яким розумним вказівником використовувати, - це питання власності . Що стосується управління ресурсами, об'єкт A володіє об'єктом B, якщо він контролює термін експлуатації об'єкта B. Наприклад, змінні учасника належать їх відповідним об'єктам, оскільки термін експлуатації змінних членів пов'язаний з терміном експлуатації об'єкта. Ви вибираєте розумні покажчики на основі того, яким об’єктом володіє.

Зауважте, що право власності на програмну систему є окремим від власності, як ми думали б про це поза програмним забезпеченням. Наприклад, людина може "володіти" своїм будинком, але це не обов'язково означає, що Personоб'єкт має контроль над життям Houseоб'єкта. Поєднання цих концепцій реального світу з поняттями програмного забезпечення - це надійний спосіб запрограмувати себе в норі.


Якщо ви маєте єдину власність на об’єкт, використовуйте std::unique_ptr<T>.

Якщо ви маєте спільне право власності на об’єкт ...
- Якщо у власності немає циклів, використовуйте std::shared_ptr<T>.
- Якщо є цикли, визначте «напрямок» і використовуйте std::shared_ptr<T>в одному напрямку та std::weak_ptr<T>в іншому.

Якщо вам належить об’єкт, але є потенціал відсутності власника, використовуйте звичайні вказівники T*(наприклад, батьківські вказівники).

Якщо вам належить об'єкт (або іншим чином гарантоване існування), використовуйте посилання T&.


Caveat: Будьте в курсі витрат на розумні покажчики. У обмеженій пам'яті або обмеженій продуктивності може бути корисним просто використовувати звичайні покажчики з більш ручною схемою управління пам'яттю.

Витрати:

  • Якщо у вас є спеціальний делетер (наприклад, ви використовуєте пули розподілу), то це матиме накладні витрати на вказівник, що може бути легко уникнути ручним видаленням.
  • std::shared_ptrмає накладні надбавки відліку посилання на копію плюс декремент про знищення з подальшим перевіркою 0-лічильників із видаленням об'єкта, що утримується. Залежно від впровадження, це може розмити ваш код і викликати проблеми з продуктивністю.
  • Час компіляції. Як і у всіх шаблонах, розумні покажчики негативно сприяють складанню часу.

Приклади:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

Бінарне дерево не має свого батьківського, але існування дерева передбачає існування його батьківського (або nullptrдля кореневого), тож використовує звичайний покажчик. Бінарне дерево (із значенням семантики) має єдине право власності на своїх дітей, тому вони є std::unique_ptr.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Тут вузол списку володіє своїм наступним та попереднім списками, тому ми визначаємо напрямок та використовуємо shared_ptrдля наступного та weak_ptrпопереднього, щоб перервати цикл.


3
Для прикладу двійкового дерева деякі люди пропонують використовувати shared_ptr<BinaryTree>для дітей та weak_ptr<BinaryTree>для батьківських стосунків.
Девід Родрігес - дрибес

@ DavidRodríguez-dribeas: Це залежить від того, чи має Дерево значення семантики чи ні. Якщо люди будуть посилатися на ваше дерево зовні навіть після того, як дерево-джерело буде знищено, то так, найкращим буде комбінований / слабкий комбо вказівник.
Петро Олександр

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

1
Якщо ви використовуєте посилання, ви ніколи не можете змінити батьківську, що може або не перешкоджає дизайну. Для врівноваження дерев це може заважати.
Mooing Duck

3
+1, але вам слід додати визначення "власності" в першому рядку. Мені часто доводиться чітко заявляти, що мова йде про життя та смерть об'єкта, а не про власність у більш доменному сенсі.
Клаїм

19

Використовуйте unique_ptr<T>весь час, за винятком випадків, коли вам потрібен підрахунок посилань, в цьому випадку використовуйте shared_ptr<T>(і в дуже рідкісних випадках, weak_ptr<T>щоб запобігти еталонним циклам). Майже в кожному випадку передача унікального права власності просто чудова.

Сирі покажчики: Добре, лише якщо вам потрібні коваріантні повернення, невласне вказівка ​​яких може статися. Інакше вони не страшно корисні.

Покажчики масиву: unique_ptrмає спеціалізацію, для T[]якої автоматично викликає delete[]результат, тому ви можете сміливо робити, unique_ptr<int[]> p(new int[42]);наприклад. shared_ptrвам все одно знадобиться спеціальний видалювач, але вам не знадобиться спеціалізований спільний або унікальний вказівник масиву. Звичайно, такі речі зазвичай краще std::vectorвсе-таки замінити . На жаль shared_ptr, не надає функцію доступу до масиву, тому вам все одно доведеться дзвонити вручну get(), але unique_ptr<T[]>надає operator[]замість operator*і operator->. У будь-якому випадку, ви повинні гранично перевірити себе. Це робить shared_ptrтрохи менш зручним для користувачів, хоча, мабуть, загальною перевагою та відсутністю залежностей від Boost, знову unique_ptrта shared_ptrпереможцями.

Розміри покажчиків: зроблено невідповідними unique_ptr, як і auto_ptr.

Тут насправді нічого більше немає. У C ++ 03 без семантики руху ця ситуація була дуже складною, але в C ++ 11 порада дуже проста.

Існує ще використання інших смарт-покажчиків, як-от intrusive_ptrабо interprocess_ptr. Однак вони дуже ніші і зовсім непотрібні в загальному випадку.


Також сирі покажчики на ітерацію. І для буферів вихідних параметрів, де буфер належить абоненту.
Бен Войгт

Хм, як я це прочитав, це ситуації, які є і коваріантним поверненням, і невладанням. Перезапис може бути хорошим, якщо ви мали на увазі союз, а не перетин. Я б також сказав, що ітерацію також варто згадати.
Бен Войгт

2
std::unique_ptr<T[]>забезпечує operator[]замість operator*і operator->. Це правда, що вам все-таки потрібно обов’язково перевірити себе.
Xeo

8

Випадки використання unique_ptr:

  • Фабричні методи
  • Учасники, які вказують (додається pimpl)
  • Збереження покажчиків у stl-контейнерах (щоб уникнути рухів)
  • Використання великих локальних динамічних об'єктів

Випадки використання shared_ptr:

  • Обмін предметами по потоках
  • Спільний доступ до об'єктів

Випадки використання weak_ptr:

  • Велика карта, яка виступає загальною орієнтиром (наприклад, карта всіх відкритих сокетів)

Не соромтесь редагувати та додавати більше


Мені насправді більше подобається ваша відповідь, коли ви даєте сценарії.
Ніколас Хамфрі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.