Когда используется std:: weak_ptr?
Я начал изучать интеллектуальные указатели С++ 11, и я не вижу полезного использования std::weak_ptr
. Может ли кто-нибудь сказать мне, когда std::weak_ptr
полезно/необходимо?
Ответы
Ответ 1
Хорошим примером может быть кеш.
Для недавно доступных объектов вы хотите сохранить их в памяти, поэтому у вас есть сильный указатель на них. Периодически вы проверяете кеш и решаете, какие объекты не были доступны в последнее время. Вам не нужно сохранять их в памяти, поэтому вы избавляетесь от сильного указателя.
Но что, если этот объект используется, а какой-то другой код содержит сильный указатель на него? Если кеш избавляется от единственного указателя на объект, он никогда не сможет найти его снова. Таким образом, кеш хранит слабый указатель на объекты, которые ему нужно найти, если они остаются в памяти.
Это именно то, что делает слабый указатель - он позволяет вам находить объект, если он все еще вокруг, но не поддерживает его, если ему больше ничего не нужно.
Ответ 2
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";
}
Ответ 3
Еще один ответ, надеюсь, проще. (для коллег по Google)
Предположим, у вас есть объекты Team
и Member
.
Очевидно, это отношения: объект Team
будет иметь указатели на своих Members
. И вполне вероятно, что члены также будут иметь обратный указатель на свой объект Team
.
Тогда у вас есть цикл зависимости. Если вы используете shared_ptr
, объекты больше не будут автоматически освобождаться, когда вы откажетесь от ссылки на них, потому что они ссылаются друг на друга циклически. Это утечка памяти.
Вы weak_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 failed if parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
Ответ 4
Вот один пример, приведенный мне by @jleahy: предположим, что у вас есть набор задач, выполняемых асинхронно и управляемый std::shared_ptr<Task>
. Возможно, вы захотите что-то делать с этими задачами периодически, поэтому событие таймера может пересечь std::vector<std::weak_ptr<Task>>
и дать задачам что-то делать. Однако одновременно задача может одновременно решить, что она больше не нужна и умирает. Таким образом, таймер может проверить, остается ли задача еще жива, сделав общий указатель от слабого указателя и используя этот общий указатель, если он не является нулевым.
Ответ 5
Они полезны с 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, где ожидающий асинхронный обработчик не продлит срок жизни целевого объекта, но все еще безопасен, если целевой объект удален.
Ответ 6
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());
Ответ 7
shared_ptr: содержит реальный объект.
weak_ptr: использует lock
для соединения с реальным владельцем или возвращает NULL в противном случае.
Грубо говоря, weak_ptr
похожа на роль агентства недвижимости. Без агентов, чтобы получить дом в аренду, нам, возможно, придется проверять случайные дома в городе. Агенты следят за тем, чтобы мы посещали только те дома, которые еще доступны и доступны для аренды.
Ответ 8
При использовании указателей важно понимать различные типы доступных указателей и когда имеет смысл использовать каждый из них. Существует четыре типа указателей в двух категориях:
- Сырые указатели:
- Необработанный указатель [т.е.
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") обеспечивают поведение указателей "без костей" и являются распространенным источником ошибок и утечек памяти. Необработанные указатели не предоставляют средств для отслеживания владения ресурсом, и разработчики должны вызывать "delete" вручную, чтобы убедиться, что они не создают утечку памяти. Это становится трудным, если ресурс используется совместно, так как бывает сложно узнать, указывают ли какие-либо объекты на ресурс. По этим причинам следует избегать необработанных указателей и использовать их только в критически важных для кода разделах кода с ограниченной областью действия.
Уникальные указатели являются базовым интеллектуальным указателем, который "владеет" базовым необработанным указателем на ресурс и отвечает за вызов удаления и освобождение выделенной памяти, как только объект, которому "принадлежит" уникальный указатель, выходит из области видимости. Название "уникальный" относится к тому факту, что только один объект может "владеть" уникальным указателем в данный момент времени. Владение может быть передано другому объекту с помощью команды перемещения, но уникальный указатель никогда не может быть скопирован или передан. По этим причинам уникальные указатели являются хорошей альтернативой необработанным указателям в том случае, если указатель нужен только одному объекту в определенный момент времени, и это освобождает разработчика от необходимости освобождать память в конце жизненного цикла объекта-владельца.
Общие указатели - это еще один тип интеллектуальных указателей, которые похожи на уникальные указатели, но позволяют многим объектам владеть общим указателем. Как и уникальный указатель, совместно используемые указатели отвечают за освобождение выделенной памяти, как только все объекты завершены, указывая на ресурс. Это достигается с помощью метода, называемого подсчетом ссылок. Каждый раз, когда новый объект становится владельцем общего указателя, счетчик ссылок увеличивается на единицу. Точно так же, когда объект выходит из области видимости или перестает указывать на ресурс, счетчик ссылок уменьшается на единицу. Когда счетчик ссылок достигает нуля, выделенная память освобождается. По этим причинам общие указатели являются очень мощным типом интеллектуальных указателей, которые следует использовать всякий раз, когда несколько объектов должны указывать на один и тот же ресурс.
Наконец, слабые указатели - это еще один тип интеллектуальных указателей, которые вместо того, чтобы указывать на ресурс напрямую, указывают на другой указатель (слабый или общий). Слабые указатели не могут получить доступ к объекту напрямую, но они могут определить, существует ли объект до сих пор или срок его действия истек. Слабый указатель может быть временно преобразован в общий указатель для доступа к указанному объекту (при условии, что он все еще существует). Чтобы проиллюстрировать это, рассмотрим следующий пример:
- Вы заняты и у вас совпадающие собрания: Встреча А и Встреча Б
- Вы решили пойти на Собрание А, а ваш коллега - на Собрание Б
- Вы говорите своему коллеге, что если собрание B все еще продолжается после окончания собрания A, вы присоединитесь
- Возможны следующие два сценария:
- Совещание A заканчивается, а собрание B все еще продолжается, поэтому вы присоединяетесь
- Совещание A заканчивается, и собрание B также завершено, поэтому вы не можете присоединиться
В этом примере у вас есть слабый указатель на собрание B. Вы не являетесь "владельцем" собрания B, поэтому оно может закончиться без вас, и вы не будете знать, закончилось ли оно или нет, пока вы не проверите. Если это не закончилось, вы можете присоединиться и участвовать, в противном случае вы не можете. Это отличается от наличия общего указателя на собрание B, поскольку в этом случае вы будете "владельцем" как на собрании A, так и на собрании B (участвуя в обоих одновременно).
В этом примере показано, как работает слабый указатель, и это полезно, когда объект должен быть сторонним наблюдателем, но не хочет, чтобы ответственность разделяла владение. Это особенно полезно в сценарии, когда два объекта должны указывать друг на друга (круговая ссылка). При использовании общих указателей ни один объект не может быть освобожден, поскольку они все еще "сильно" указаны другим объектом. Когда один из указателей является слабым указателем, объект, содержащий слабый указатель, может при необходимости обращаться к другому объекту при условии, что он все еще существует.
Ответ 9
Помимо других уже упомянутых допустимых вариантов использования std::weak_ptr
является std::weak_ptr
инструментом в многопоточной среде, потому что
- Он не владеет объектом и поэтому не может помешать удалению в другом потоке.
-
std::shared_ptr
в сочетании с std::weak_ptr
безопасен от висячих указателей - в std::weak_ptr
от std::unique_ptr
в сочетании с необработанными указателями -
std::weak_ptr::lock()
- это атомарная операция (см. также О безопасности потоков в weak_ptr)
Рассмотрим задачу по одновременной загрузке всех изображений каталога (~ 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 проверяет, не истек ли срок действия указателя-владельца.
Ответ 10
http://en.cppreference.com/w/cpp/memory/weak_ptr
std:: weak_ptr - это умный указатель, который содержит ссылку на не принадлежащую ( "слабую" ) ссылку на объект, который управляется std:: shared_ptr. Он должен быть преобразован в std:: shared_ptr для доступа к указанному объекту.
std:: weak_ptr моделирует временное владение: когда объект должен быть доступен только в том случае, если он существует, и он может быть удален в любой момент кем-то другим, std:: weak_ptr используется для отслеживания объекта и преобразуется to std:: shared_ptr, чтобы принять временное владение. Если исходный std:: shared_ptr будет уничтожен в это время, время жизни объекта будет расширено до тех пор, пока не будет уничтожен временный std:: shared_ptr.
Кроме того, std:: weak_ptr используется для разрыва круговых ссылок на std:: shared_ptr.
Ответ 11
Недостатком разделяемого указателя является то, что shared_pointer не может обработать зависимость родительско-дочернего цикла. Означает, если родительский класс использует объект дочернего класса, используя общий указатель, в том же файле, если дочерний класс использует объект родительского класса. Общий указатель не сможет уничтожить все объекты, даже общий указатель вообще не вызывает деструктор в сценарии зависимости цикла. в основном разделяемый указатель не поддерживает механизм подсчета ссылок.
Этот недостаток мы можем преодолеть с помощью weak_pointer.
Ответ 12
Когда мы не хотим владеть объектом:
Пример:
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 никогда не удаляются и, следовательно, происходит утечка памяти.
Чтобы избежать такой проблемы, просто используйте weak_ptr в классе A вместо shared_ptr, что имеет больше смысла.
Ответ 13
Я вижу 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 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 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 talk).
struct Node {
std::weak_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
Херб Саттер отлично говорит о том, как лучше всего использовать языковые функции (в данном случае умные указатели), чтобы обеспечить утечку свободы по умолчанию (имеется в виду: все защелкивается на месте; вы вряд ли можете облажаться) Это нужно смотреть.