shared_ptr та слабкі_ptr відмінності


76

Я читаю книгу Скотта Майерса "Ефективний C ++". Було згадано, що існують tr1::shared_ptrі tr1::weak_ptrдіють як вбудовані вказівники, але вони відстежують, скільки tr1::shared_ptrsвказує на об’єкт.

Це називається підрахунком посилань. Це добре допомагає запобігти витоку ресурсів в ациклічних структурах даних, але якщо два або більше об'єктів містять tr1::shared_ptrsтаке, що формується цикл, цикл може тримати кількість посилань один одного вище нуля, навіть коли всі зовнішні вказівники на цикл були знищені.

Ось де tr1::weak_ptrsзаходьте.

Моє питання полягає в тому, як циклічні структури даних роблять кількість посилань вище нуля. Я прошу просити приклад програми на C ++. Як вирішується проблема weak_ptrs? (ще раз, із прикладом, будь ласка).


« Як проблему вирішує слабкий_ппрс? » Ні. Це вирішується належним дизайном, де не існує циклу власності.
curiousguy

Відповіді:


52

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

weak_ptrвказує на a, shared_ptrале не збільшує кількість посилань. Це означає, що об'єкт, що піддається, все ще може бути видалений, навіть якщо на нього є weak_ptrпосилання.

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


10
Я вважаю, що контрольний об'єкт посилання веде підрахунок як "Використання" (shared_ptrs), так і "Слабкого" (слабо_ptrs + (Використання> 0? 1: 0)). Але це може бути деталізованою деталлю.
Ben L

121

Дозвольте мені повторити ваше запитання: "Моє запитання, як циклічна структура даних робить кількість посилань вище нуля, просимо показати на прикладі в програмі C ++. Як проблема вирішується ще weak_ptrsраз на прикладі, будь ласка."

Проблема виникає з таким кодом на C ++ (концептуально):

class A { shared_ptr<B> b; ... };
class B { shared_ptr<A> a; ... };
shared_ptr<A> x(new A);  // +1
x->b = new B;            // +1
x->b->a = x;             // +1
// Ref count of 'x' is 2.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, there will be a memory leak:
// 2 is decremented to 1, and so both ref counts will be 1.
// (Memory is deallocated only when ref count drops to 0)

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

Щоб її вирішити, програміст повинен знати про відносини власності між об’єктами або повинен винайти відносини власності, якщо таких форм власності природно не існує.

Наведений вище код C ++ можна змінити так, щоб A володів B:

class A { shared_ptr<B> b; ... };
class B { weak_ptr<A>   a; ... };
shared_ptr<A> x(new A); // +1
x->b = new B;           // +1
x->b->a = x;            // No +1 here
// Ref count of 'x' is 1.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, its ref count will drop to 0.
// While destroying it, ref count of 'x->b' will drop to 0.
// So both A and B will be deallocated.

Найважливіше питання: чи weak_ptrможна використовувати, якщо програміст не може визначити відносини власності та не може встановити будь-яке статичне право власності через відсутність привілеїв чи відсутність інформації?

Відповідь така: якщо право власності на об’єкти незрозуміле, weak_ptr це не може допомогти. Якщо є цикл, програміст повинен його знайти і розірвати. Альтернативним засобом є використання мови програмування з повним збиранням сміття (наприклад: Java, C #, Go, Haskell) або використання консервативного (= недосконалого) збирача сміття, який працює з C / C ++ (наприклад: Boehm GC) .


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

3
Якщо B :: a є слабким_ptr, то ніщо не повинно залежати від його існування - оскільки воно не є власником a. A :: b - тут надійний хлопець.
багатий.

4
Я вибрав би вашу як найкращу відповідь. Але ей .. не мій вибір :) +1 btw
Пол

18

Для майбутніх читачів.
Просто хочу зазначити, що пояснення, дане Atom, є чудовим, ось робочий код

#include <memory> // and others
using namespace std;

    class B; // forward declaration 
    // for clarity, add explicit destructor to see that they are not called
    class A { public: shared_ptr<B> b; ~A() {cout << "~A()" << endl; } };  
    class B { public: shared_ptr<A> a; ~B() {cout << "~B()" << endl; } };     
    shared_ptr<A> x(new A);  //x->b share_ptr is default initialized
    x->b = make_shared<B>(); // you can't do "= new B" on shared_ptr                      
    x->b->a = x;
    cout << x.use_count() << endl;  

Slab_ptr призначений лише для циклічного посилання або кешу?
g10guang,

7

Слабкі вказівники просто «спостерігають» за керованим об’єктом; вони не «тримають його в живих» і не впливають на його життя. На відміну від того shared_ptr, коли останній weak_ptrвиходить за межі дії або зникає, вказаний об’єкт все ще може існувати, оскільки weak_ptrце не впливає на термін служби об’єкта - він не має прав власності. weak_ptrМоже бути використано для визначення того, чи існує об'єкт, і для забезпечення , shared_ptrякі можуть бути використані , щоб посилатися на нього.

Визначення weak_ptrрозроблено для того, щоб зробити його відносно надійним, тому в результаті дуже мало можна зробити безпосередньо з weak_ptr. Наприклад, ви не можете розмежувати його; ані operator*не operator->визначено для a weak_ptr. Ви не можете отримати до нього вказівник на об’єкт - немає get()функції. Визначено функцію порівняння, яку ви можете зберігати weak_ptrsу впорядкованому контейнері, але це все.


-6

Усі вищезазначені відповіді НЕПРАВИЛЬНІ. weak_ptrНЕ використовується для розриву циклічних посилань, вони мають інше призначення.

В основному, якщо всі shared_ptr(s)були створені make_shared()або allocate_shared()викликають, вам НІКОЛИ не знадобиться, weak_ptrякщо у вас немає іншого ресурсу, крім пам'яті, для управління. Ці функції створюють shared_ptrоб'єкт лічильника посилань із самим об'єктом, і пам'ять одночасно звільняється.

Єдина відмінність між weak_ptrта shared_ptrполягає в тому, що weak_ptrдозволяє об'єкт лічильника посилань зберігатися після звільнення фактичного об'єкта. Як результат, якщо ви зберігаєте багато shared_ptrв себе, std::setфактичні об'єкти займуть багато пам'яті, якщо вони досить великі. Цю проблему можна вирішити, weak_ptrзамість цього. У цьому випадку вам слід переконатися, що термін weak_ptrзберігання в контейнері не закінчився, перш ніж використовувати його.


" слабкий_ппр НЕ використовується для розбиття циклічних посилань " +1 " вони мають іншу мету. ", але ця відповідь не пояснює мету випуску, тому -1
curiousguy

" якщо всі shared_ptr (s) були створені за допомогою make_shared () ", тоді вам не потрібно shared_ptr( unique_ptrце більш доречно). Значення підрахунку посилань - це коли кілька об’єктів містять посилання. Це відбувається за допомогою доручень, як дуже чудовий приклад.
jwm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.