Коли корисний std :: слаб_ptr?


268

Я почав вивчати розумні покажчики на C ++ 11 і не бачу корисного використання std::weak_ptr. Може хтось скаже мені, коли std::weak_ptrце корисно / потрібно?


Відповіді:


231

Хорошим прикладом може бути кеш.

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

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

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


8
Отже, std :: wake_ptr може вказувати лише там, де вказує інший покажчик, і він вказує на nullptr, коли вказаний об'єкт видалено / не вказується більше жодними іншими покажчиками?

27
@RM: В основному, так. Коли у вас слабкий покажчик, ви можете спробувати просунути його до сильного вказівника. Якщо цей об'єкт все ще існує (оскільки принаймні один сильний покажчик на нього існує все-таки), ця операція успішна і дає вам сильний покажчик на неї. Якщо цього об’єкта не існує (оскільки всі сильні вказівники відійшли), то ця операція не працює (і зазвичай ви реагуєте, викинувши слабкий покажчик).
Девід Шварц

12
Хоча сильний покажчик підтримує об’єкт живим, слабкий_птр може дивитись на нього ... не пов'язуючи його з часом життя.
The Vivandiere

3
Інший приклад, який я використовував принаймні кілька разів, - це при впровадженні спостерігачів, іноді стає зручно змусити суб'єкта вести список слабких покажчиків і робити власне очищення списку. Це економить трохи зусиль, явно видаляючи спостерігачів, коли вони видаляються, і, що значно важливіше, вам не потрібно мати інформацію про наявні предмети при знищенні спостерігачів, що, як правило, значно спрощує речі.
Джейсон C

3
Зачекайте, що не так у кеші, який містить shared_ptr і просто видаливши його зі свого списку, коли його слід очистити з пам'яті? Будь-які користувачі будуть зберігати спільний_ptr все одно, і кешований ресурс буде очищений, як тільки всі користувачі закінчать з ним.
rubenvb

299

std::weak_ptrце дуже хороший спосіб вирішити звисаючий покажчик . Лише використовуючи необроблені покажчики, неможливо дізнатися, чи були зазначені дані розміщені чи ні. Замість цього, дозволяючи std::shared_ptrуправляти даними, а також надання std::weak_ptrкористувачам даних, користувачі можуть перевірити достовірність даних по телефону expired()або lock().

Ви не могли зробити це std::shared_ptrсамостійно, оскільки всі std::shared_ptrекземпляри поділяють право власності на дані, які не видаляються до того, як std::shared_ptrбудуть видалені всі екземпляри . Ось приклад того, як перевірити наявність висячого вказівника за допомогою lock():

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

1
Гаразд, це як якщо ви локально встановили (володіє) покажчиком на нуль (видаліть пам'ять), всі інші (слабкі) покажчики на ту саму пам'ять також встановлені на нуль
Pat-Laugh

std::weak_ptr::lockстворює новий, std::shared_ptrякий розділяє право власності на керований об’єкт.
Сахіб Яр

129

Ще одна відповідь, сподіваюсь, простіша. (для колег Google

Припустимо, у вас є Teamі Memberоб’єкти.

Очевидно, що це стосунки: Teamоб’єкт матиме вказівники на його Members. І цілком ймовірно, що члени також матимуть задній вказівник на свій Teamоб’єкт.

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

Ви зламаєте це за допомогою weak_ptr. «Власник» , як правило , використовує shared_ptrі «належить» використовувати weak_ptrйого батько, і перетворити його тимчасово в , shared_ptrколи йому потрібен доступ до його батьків.

Зберігати слабкий ptr:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

потім використовуйте його за потреби

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope

1
Як це витік пам'яті? Якщо команда буде знищена, вона знищить своїх членів, таким чином, кількість посилань ref_ shared_ptr буде 0, а також знищено?
Польма

4
@paulm Team не знищить "своїх" членів. Вся справа в shared_ptrтому, щоб розділити право власності, тому ніхто не несе особливої ​​відповідальності за звільнення пам’яті, вона звільняється автоматично, коли вона більше не використовується. Якщо немає циклу ... У вас може бути кілька команд, які діляться на одного гравця (минулі команди?). Якщо об'єкт команди "володіє" членами, то shared_ptrдля початку немає необхідності використовувати .
Offirmo

1
Він не знищить їх, але його shared_ptr вийде за межі з ним, зменшує use_count, таким чином, в цей момент use_count дорівнює 0, і тому shared_ptr видалить те, на що вказує?
Польма

2
@paulm Ви праві. Але оскільки в цьому прикладі на команду також shared_ptrпосилаються її "члени команди", коли вона буде знищена? Те, що ви описуєте, - це випадок, коли немає циклу.
Offirmo

14
Це не так вже й погано, я б подумав. Якщо член може належати до багатьох команд, використання довідки не буде працювати.
Мазюд

22

Ось один із прикладів, поданий мені @jleahy: Припустимо, у вас є колекція завдань, виконана асинхронно та керована std::shared_ptr<Task>. Ви можете періодично щось робити з цими завданнями, тому подія таймера може пройти через A std::vector<std::weak_ptr<Task>>і дати завданням щось робити. Однак одночасно завдання, можливо, одночасно вирішило, що воно більше не потрібно і померти. Таким чином, таймер може перевірити, чи є завдання ще живим, зробивши загальний покажчик зі слабкого вказівника та використовуючи цей спільний покажчик, за умови, що це недійсне.


4
: Здається, що це гарний приклад, але чи можете ви, будь ласка, докладно розглянути ваш приклад? Я думаю, коли завдання буде завершено, його вже слід видалити з std :: vector <std :: слаб_ptr <Завдання>> без періодичної перевірки. Тож не впевнений, що тут дуже корисний std :: vector <std :: слаб_ptr <>>.
Gob00st

Аналогічний коментар з чергами: скажіть, що у вас є об’єкти, і ви ставите їх у чергу на якийсь ресурс, об’єкти можна буде видалити під час очікування. Отже, якщо ви ставите в чергу слабкі_птри, вам не доведеться турбуватися з видаленням записів з черги. Weak_ptrs буде визнано недійсним, а потім буде відкинуто під час кодування.
zzz777

1
@ zzz777: Логіка, яка визнає об'єкти недійсними, може навіть не знати про існування черги або вектора спостерігачів. Тож спостерігач виконує окрему петлю над слабкими покажчиками, діючи на ті, що ще живі, і виймаючи мертвих з контейнера ...
Керрек С.Б.

1
@KerekSB: так, і якщо у черзі вам навіть не потрібно окремий цикл - тоді доступний ресурс, ви викидаєте минулі терміни слабких_птрів (якщо такі є), поки ви не отримали дійсний (якщо такий є).
zzz777

Ви також можете змусити потоки видалити себе з колекції, але це створило б залежність і вимагало блокування.
цікавогут

16

Вони корисні з Boost.Asio, коли ви не впевнені, що цільовий об'єкт все ще існує, коли викликається асинхронний обробник. Хитрість полягає в тому, щоб прив’язати об'єкт weak_ptrдо асинхонного обробника, використовуючи std::bindабо лямбда-захоплення.

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

Це варіант self = shared_from_this()ідіоми, часто зустрічається в прикладах Boost.Asio, коли очікуваний асинхронний обробник не продовжить термін експлуатації цільового об'єкта, але все ще є безпечним, якщо цільовий об’єкт буде видалений.


Чому this
знадобилося

@Orwellophile виправлено. Сила звички при використанні self = shared_from_this()ідіоми, коли обробник викликає методи в межах одного класу.
Еміль Корм'є

16

shared_ptr : вміщує реальний об'єкт.

слабкий_птр : використовується lockдля підключення до реального власника або повернення NULL в shared_ptrіншому випадку.

слабкий ptr

Грубо кажучи, weak_ptrроль подібна до ролі житлового агентства . Без агентів, щоб отримати будинок в оренду, можливо, доведеться перевірити випадкові будинки в місті. Агенти впевнені, що ми відвідуємо лише ті будинки, які все ще доступні та доступні для оренди.


14

weak_ptrтакож добре перевірити правильність видалення об'єкта - особливо в одиничних тестах. Типовий випадок використання може виглядати так:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());

13

Під час використання покажчиків важливо розуміти різні типи покажчиків і коли є сенс використовувати кожен з них. Існує чотири типи покажчиків у двох категоріях:

  • Сирі покажчики:
    • Сирий вказівник [тобто SomeClass* ptrToSomeClass = new SomeClass();]
  • Розумні покажчики:
    • Унікальні покажчики [тобто
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Спільні покажчики [тобто
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Слабкі покажчики [тобто
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

Сирі покажчики (іноді їх називають "застарілими вказівниками" або "вказівниками C") забезпечують поведінку вказівників "голими кістками" і є загальним джерелом помилок та витоку пам'яті. Сировимі вказівниками не передбачено можливості відстежувати право власності на ресурс, і розробники повинні викликати "видалити" вручну, щоб переконатися, що вони не створюють витоку пам'яті. Це стає важким, якщо ресурс спільний, оскільки може бути складним питання про те, чи все ще якісь об'єкти вказують на ресурс. З цієї причини, як правило, слід уникати необроблених покажчиків і використовувати їх лише у критичних для продуктивності розділах коду з обмеженою сферою застосування.

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

Спільні покажчики - це ще один тип розумних покажчиків, схожих на унікальні вказівники, але дозволяють багатьом об’єктам володіти правами на спільний покажчик. Як і унікальний вказівник, спільні покажчики відповідають за звільнення виділеної пам'яті, коли всі об’єкти зроблені вказують на ресурс. Це досягається методом, званим опорним підрахунком. Кожен раз, коли новий об’єкт приймає право власності на загальний покажчик, кількість посилань збільшується на одиницю. Аналогічно, коли об'єкт виходить за межі або перестає вказувати на ресурс, кількість відліку зменшується на одиницю. Коли число відліку досягає нуля, виділена пам'ять звільняється. З цієї причини загальні вказівники - це дуже потужний тип інтелектуального вказівника, який слід використовувати будь-коли, коли кілька об'єктів потребують вказівки на один і той же ресурс.

Нарешті, слабкі покажчики - це ще один тип розумних покажчиків, які, замість того, щоб безпосередньо вказувати на ресурс, вказують на інший покажчик (слабкий або загальний). Слабкі вказівники не можуть отримати доступ до об'єкта безпосередньо, але вони можуть визначити, чи існує об'єкт все ще чи він закінчився. Слабкий покажчик може бути тимчасово перетворений у спільний вказівник для доступу до об'єкта, що вказує на об'єкт (за умови, що він все ще існує). Для ілюстрації розглянемо наступний приклад:

  • Ви зайняті і проводите зустрічі, що перетинаються: зустріч А та зустріч Б
  • Ви вирішили перейти на нараду А, а ваш колега піде на нараду
  • Ви скажете своєму колезі, що якщо зустріч B все-таки відбудеться після закінчення зустрічі А, ви приєднаєтесь
  • Наступні два сценарії можуть розігратися:
    • Зустріч A закінчується, а зустріч B все ще триває, тому ви приєднуєтесь
    • Зустріч А закінчується і Зустріч В також закінчилася, тому ви не можете приєднатися

У прикладі у вас слабкий вказівник на зустріч B. Ви не є "власником" у зустрічі B, тому це може закінчитися без вас, і ви не знаєте, закінчилось це чи ні, якщо ви не перевірите. Якщо це не закінчилося, ви можете приєднатись до участі, інакше не можете. Це відрізняється від спільного вказівника на зустріч B, оскільки ви тоді будете "власником" як у зустрічі A, так і у зустрічі B (беручи участь в обох одночасно).

Приклад ілюструє, як слабкий покажчик працює і є корисним, коли об’єкт повинен бути стороннім спостерігачем , але не хоче відповідальності за розподіл права власності. Це особливо корисно в сценарії, коли два об’єкти потрібно вказувати один на одного (він же круговий посилання). За допомогою загальних покажчиків жоден об'єкт не може бути випущений, оскільки він ще "сильно" вказаний іншим об'єктом. Коли один з покажчиків є слабким вказівником, об’єкт, утримуючи слабкий покажчик, все ще може отримати доступ до іншого об'єкта, коли це необхідно, за умови, що він все ще існує.


6

Окрім інших уже згаданих дійсних випадків використання std::weak_ptr, це чудовий інструмент у багатопотоковому середовищі, оскільки

  • Він не володіє об'єктом, і тому не може перешкоджати видаленню в іншому потоці
  • std::shared_ptrу поєднанні з std::weak_ptrбезпечним від звисаючих покажчиків - навпротиstd::unique_ptr в поєднанні з сирими вказівниками
  • std::weak_ptr::lock()є атомною операцією (див. також Про безпеку потоку слабких_птрів )

Розгляньте завдання завантажити всі зображення каталогу (~ 10.000) одночасно в пам'ять (наприклад, як кеш-мініатюр). Очевидно, найкращий спосіб зробити це контрольна нитка, яка обробляє зображення та керує ними, і декілька робочих потоків, які завантажують зображення. Тепер це легке завдання. Ось дуже спрощена реалізація ( join()пропущено і т. Д., Нитки повинні оброблятися по-різному в реальній реалізації тощо)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

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

Вам потрібно буде зв’язок з потоком і вам доведеться зупинити всі потоки завантажувача, перш ніж ви можете змінити свою m_imageDatas поле. Інакше навантажувачі здійснюватимуть завантаження до тих пір, поки всі зображення не будуть виконані - навіть якщо вони вже застаріли. У спрощеному прикладі це не буде надто важко, але в реальному середовищі все може бути набагато складніше.

Нитки, ймовірно, будуть частиною пулу потоків, що використовується декількома менеджерами, з яких деякі зупиняються, а інші - ні. Простий параметр imagesToLoadбуде заблокованою чергою, в яку ці менеджери висувають свої запити на зображення з різних потоків управління а читачі спливають запити - у довільному порядку - на іншому кінці. І тому спілкування стає важким, повільним та схильним до помилок. Дуже елегантним способом уникнути будь-якого додаткового спілкування в таких випадках є використання std::shared_ptrспільно з std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

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


2

http://en.cppreference.com/w/cpp/memory/weak_ptr std :: слабкий_ptr - розумний покажчик, який містить невласницьку ("слабку") посилання на об'єкт, яким керує std :: shared_ptr. Його потрібно перетворити на std :: shared_ptr для доступу до об'єкта, на який посилається.

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

Крім того, std :: слабы_ptr використовується для розбиття кругових посилань на std :: shared_ptr.


" зламати кругові посилання " як?
цікавийгун

2

Існує недолік спільного вказівника: shared_pointer не може впоратися із залежністю циклу батько-дитина. Значить, якщо батьківський клас використовує об'єкт дочірнього класу за допомогою спільного вказівника, у тому самому файлі, якщо дочірній клас використовує об'єкт батьківського класу. Спільний покажчик не зможе знищити всі об'єкти, навіть спільний покажчик зовсім не викликає деструктора в сценарії залежності циклу. в основному загальний покажчик не підтримує механізм відліку посилань.

Цей недолік ми можемо подолати, використовуючи слабкий_показчик.


Як слабкий довідник може вирішити кругову залежність?
curiousguy

1
@curiousguy, дитина використовує слабке посилання на батьків, тоді батько може бути розміщений, коли немає спільних (сильних) посилань, які вказують на нього. Таким чином, під час доступу до батька через дитину, слабке посилання має перевірятись, чи є батько ще доступним. Альтернативно, щоб уникнути цього додаткового стану, круговий механізм відстеження опорних значень (або розмітка позначок, або зондування декрементів знижок, обидва з яких мають погану асимптотичну ефективність) може порушити циркулярні спільні посилання, коли єдині спільні посилання на батьків і дитини є від кожного інший.
Шелбі Мур III,

@ShelbyMooreIII " має перевірити, чи є батько ще доступним " так, і ви повинні вміти правильно реагувати на недоступний випадок! Що не відбувається з реальним (тобто сильним) реф. Це означає, що слабкий коефіцієнт не є падінням заміни: він вимагає зміни логіки.
допитливий хлопець

2
@curiousguy ви не запитували "Як можна weak_ptrрозібратися з круговою залежністю без зміни в логіці програми як заміни для спадання shared_ptr?" :-)
Шелбі Мур III,

2

Коли ми не хочемо володіти об'єктом:

Наприклад:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

У вищевказаному класі wPtr1 не володіє ресурсом, вказаним wPtr1. Якщо ресурс видалено, термін дії wPtr1 закінчується.

Щоб уникнути кругової залежності:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

Тепер, якщо ми робимо shared_ptr класів B і A, use_count обох покажчиків два.

Коли shared_ptr виходить із сфери дії, кількість все ще залишається 1, а значить, об'єкт A і B не видаляється.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

вихід:

A()
B()

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

Щоб уникнути такої проблеми, просто використовуйте слабкий_ptr в класі А замість shared_ptr, що має більше сенсу.


2

Я розглядаю std::weak_ptr<T>як ручку до std::shared_ptr<T>: Це дозволяє мені отримати те, std::shared_ptr<T>якщо воно все ще існує, але воно не продовжить його термін експлуатації. Існує кілька сценаріїв, коли така точка зору корисна:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

Ще один важливий сценарій - це розбивати цикли в структурах даних.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

Herb Sutter має чудову розмову, яка пояснює найкраще використання мовних функцій (в даному випадку розумних покажчиків) для забезпечення Leak Freedom за замовчуванням (мається на увазі: усе клацне на місці за конструкцією; навряд чи можна це зкрутити). Це обов'язково слідкувати.

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