Відповіді:
Хорошим прикладом може бути кеш.
Для нещодавно доступних об’єктів ви хочете зберегти їх у пам’яті, тому ви тримаєте на них сильний покажчик. Періодично ви скануєте кеш-пам'ять і вирішуєте, до яких об’єктів недавно зверталися. Вам не потрібно зберігати це в пам’яті, тому ви позбавитесь від сильного вказівника.
Але що робити, якщо цей об’єкт використовується, і якийсь інший код має чіткий вказівник на нього? Якщо кеш позбудеться свого єдиного вказівника на об’єкт, він більше ніколи не може його знайти. Таким чином, кеш зберігає слабкий вказівник на об'єкти, які йому потрібно знайти, якщо вони трапляться в пам'яті.
Це саме те, що робить слабкий покажчик - він дозволяє знаходити об'єкт, якщо він все ще знаходиться навколо, але не тримає його навколо, якщо нічого іншого не потрібно.
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";
}
std::weak_ptr::lock
створює новий, std::shared_ptr
який розділяє право власності на керований об’єкт.
Ще одна відповідь, сподіваюсь, простіша. (для колег 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
shared_ptr
тому, щоб розділити право власності, тому ніхто не несе особливої відповідальності за звільнення пам’яті, вона звільняється автоматично, коли вона більше не використовується. Якщо немає циклу ... У вас може бути кілька команд, які діляться на одного гравця (минулі команди?). Якщо об'єкт команди "володіє" членами, то shared_ptr
для початку немає необхідності використовувати .
shared_ptr
посилаються її "члени команди", коли вона буде знищена? Те, що ви описуєте, - це випадок, коли немає циклу.
Ось один із прикладів, поданий мені @jleahy: Припустимо, у вас є колекція завдань, виконана асинхронно та керована std::shared_ptr<Task>
. Ви можете періодично щось робити з цими завданнями, тому подія таймера може пройти через A std::vector<std::weak_ptr<Task>>
і дати завданням щось робити. Однак одночасно завдання, можливо, одночасно вирішило, що воно більше не потрібно і померти. Таким чином, таймер може перевірити, чи є завдання ще живим, зробивши загальний покажчик зі слабкого вказівника та використовуючи цей спільний покажчик, за умови, що це недійсне.
Вони корисні з 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
self = shared_from_this()
ідіоми, коли обробник викликає методи в межах одного класу.
shared_ptr : вміщує реальний об'єкт.
слабкий_птр : використовується lock
для підключення до реального власника або повернення NULL в shared_ptr
іншому випадку.
Грубо кажучи, weak_ptr
роль подібна до ролі житлового агентства . Без агентів, щоб отримати будинок в оренду, можливо, доведеться перевірити випадкові будинки в місті. Агенти впевнені, що ми відвідуємо лише ті будинки, які все ще доступні та доступні для оренди.
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());
Під час використання покажчиків важливо розуміти різні типи покажчиків і коли є сенс використовувати кожен з них. Існує чотири типи покажчиків у двох категоріях:
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. Ви не є "власником" у зустрічі B, тому це може закінчитися без вас, і ви не знаєте, закінчилось це чи ні, якщо ви не перевірите. Якщо це не закінчилося, ви можете приєднатись до участі, інакше не можете. Це відрізняється від спільного вказівника на зустріч B, оскільки ви тоді будете "власником" як у зустрічі A, так і у зустрічі B (беручи участь в обох одночасно).
Приклад ілюструє, як слабкий покажчик працює і є корисним, коли об’єкт повинен бути стороннім спостерігачем , але не хоче відповідальності за розподіл права власності. Це особливо корисно в сценарії, коли два об’єкти потрібно вказувати один на одного (він же круговий посилання). За допомогою загальних покажчиків жоден об'єкт не може бути випущений, оскільки він ще "сильно" вказаний іншим об'єктом. Коли один з покажчиків є слабким вказівником, об’єкт, утримуючи слабкий покажчик, все ще може отримати доступ до іншого об'єкта, коли це необхідно, за умови, що він все ще існує.
Окрім інших уже згаданих дійсних випадків використання 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 перевіряє, чи не закінчився термін дії вказівника.
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.
Існує недолік спільного вказівника: shared_pointer не може впоратися із залежністю циклу батько-дитина. Значить, якщо батьківський клас використовує об'єкт дочірнього класу за допомогою спільного вказівника, у тому самому файлі, якщо дочірній клас використовує об'єкт батьківського класу. Спільний покажчик не зможе знищити всі об'єкти, навіть спільний покажчик зовсім не викликає деструктора в сценарії залежності циклу. в основному загальний покажчик не підтримує механізм відліку посилань.
Цей недолік ми можемо подолати, використовуючи слабкий_показчик.
weak_ptr
розібратися з круговою залежністю без зміни в логіці програми як заміни для спадання shared_ptr
?" :-)
Коли ми не хочемо володіти об'єктом:
Наприклад:
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, що має більше сенсу.
Я розглядаю 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 за замовчуванням (мається на увазі: усе клацне на місці за конструкцією; навряд чи можна це зкрутити). Це обов'язково слідкувати.