Ответы:
Хорошим примером будет кеш.
Для объектов, к которым недавно обращались, вы хотите сохранить их в памяти, поэтому держите на них сильный указатель. Периодически вы сканируете кеш и решаете, к каким объектам недавно никто не обращался. Вам не нужно хранить их в памяти, поэтому вы избавляетесь от сильного указателя.
Но что, если этот объект используется и какой-то другой код содержит сильный указатель на него? Если кеш избавляется от своего единственного указателя на объект, он никогда не сможет найти его снова. Таким образом, кэш хранит слабый указатель на объекты, которые он должен найти, если они останутся в памяти.
Это именно то, что делает слабый указатель - он позволяет вам находить объект, если он все еще рядом, но не сохраняет его, если он больше не нужен.
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
том, чтобы делиться собственностью, поэтому никто не несет особой ответственности за освобождение памяти, она освобождается автоматически, когда она больше не используется. Если нет петли ... У вас может быть несколько команд, совместно использующих одного игрока (прошлые команды?). Если командный объект «владеет» участниками, то нет необходимости использовать a 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 : содержит реальный объект.
weak_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») обеспечивают поведение указателей «без костей» и являются распространенным источником ошибок и утечек памяти. Необработанные указатели не предоставляют средств для отслеживания владения ресурсом, и разработчики должны вызвать «delete» вручную, чтобы убедиться, что они не создают утечку памяти. Это становится трудным, если ресурс используется совместно, так как бывает сложно узнать, указывают ли какие-либо объекты на ресурс. По этим причинам следует избегать необработанных указателей и использовать их только в критически важных для кода разделах кода с ограниченной областью действия.
Уникальные указатели являются базовым интеллектуальным указателем, который «владеет» базовым необработанным указателем на ресурс и отвечает за вызов удаления и освобождение выделенной памяти, как только объект, которому «принадлежит» уникальный указатель, выходит из области видимости. Название «уникальный» относится к тому факту, что только один объект может «владеть» уникальным указателем в данный момент времени. Владение может быть передано другому объекту с помощью команды перемещения, но уникальный указатель никогда не может быть скопирован или передан. По этим причинам уникальные указатели являются хорошей альтернативой необработанным указателям в том случае, если указатель нужен только одному объекту в определенный момент времени, и это избавляет разработчика от необходимости освобождать память в конце жизненного цикла объекта-владельца.
Общие указатели - это еще один тип интеллектуальных указателей, которые похожи на уникальные указатели, но позволяют многим объектам владеть общим указателем. Как и уникальный указатель, совместно используемые указатели отвечают за освобождение выделенной памяти после того, как все объекты завершены, указывая на ресурс. Это достигается с помощью метода, называемого подсчетом ссылок. Каждый раз, когда новый объект становится владельцем общего указателя, счетчик ссылок увеличивается на единицу. Точно так же, когда объект выходит из области видимости или перестает указывать на ресурс, счетчик ссылок уменьшается на единицу. Когда счетчик ссылок достигает нуля, выделенная память освобождается. По этим причинам общие указатели являются очень мощным типом интеллектуального указателя, который следует использовать всякий раз, когда несколько объектов должны указывать на один и тот же ресурс.
Наконец, слабые указатели - это еще один тип интеллектуальных указателей, которые вместо прямого указания на ресурс указывают на другой указатель (слабый или общий). Слабые указатели не могут получить доступ к объекту напрямую, но они могут определить, существует ли объект до сих пор или срок его действия истек. Слабый указатель может быть временно преобразован в общий указатель для доступа к указанному объекту (при условии, что он все еще существует). Для иллюстрации рассмотрим следующий пример:
В этом примере у вас есть слабый указатель на собрание B. Вы не являетесь «владельцем» собрания B, поэтому оно может закончиться без вас, и вы не будете знать, закончилось ли оно или нет, пока вы не проверите. Если это не закончилось, вы можете присоединиться и участвовать, в противном случае вы не можете. Это отличается от наличия общего указателя на собрание B, поскольку в этом случае вы будете «владельцем» как на собрании A, так и на собрании B (участвуя в обоих одновременно).
В этом примере показано, как работает слабый указатель, и он полезен, когда объект должен быть сторонним наблюдателем , но не хочет ответственности за совместное владение. Это особенно полезно в сценарии, когда два объекта должны указывать друг на друга (круговая ссылка). При использовании общих указателей ни один объект не может быть освобожден, поскольку они все еще «сильно» указаны другим объектом. Когда один из указателей является слабым указателем, объект, содержащий слабый указатель, может при необходимости обращаться к другому объекту при условии, что он все еще существует.
Помимо других уже упомянутых допустимых вариантов использования std::weak_ptr
это замечательный инструмент в многопоточной среде, потому что
std::shared_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 проверяет, не истек ли срок действия указателя-владельца.
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptr - это интеллектуальный указатель, содержащий несобственную («слабую») ссылку на объект, управляемый std :: shared_ptr. Он должен быть преобразован в std :: shared_ptr для доступа к ссылочному объекту.
std :: weak_ptr моделирует временное владение: когда доступ к объекту требуется только в том случае, если он существует, и он может быть в любой момент удален кем-то другим, std :: weak_ptr используется для отслеживания объекта и преобразуется в std: : shared_ptr для временного владения. Если исходный std :: shared_ptr уничтожается в это время, время жизни объекта увеличивается до тех пор, пока не будет уничтожен временный std :: shared_ptr.
Кроме того, std :: weak_ptr используется для разрыва циклических ссылок на std :: shared_ptr.
Недостатком разделяемого указателя является то, что shared_pointer не может обработать зависимость родительско-дочернего цикла. Означает, если родительский класс использует объект дочернего класса, используя общий указатель, в том же файле, если дочерний класс использует объект родительского класса. Общий указатель не сможет уничтожить все объекты, даже общий указатель вообще не вызывает деструктор в сценарии зависимости цикла. в основном разделяемый указатель не поддерживает механизм подсчета ссылок.
Этот недостаток мы можем преодолеть с помощью weak_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 никогда не удаляются и, следовательно, происходит утечка памяти.
Чтобы избежать такой проблемы, просто используйте weak_ptr в классе A вместо 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;
};
Херб Саттер отлично говорит о том, как лучше всего использовать языковые функции (в данном случае умные указатели), чтобы обеспечить утечку свободы по умолчанию (имеется в виду: все защелкивается на месте по конструкции; вряд ли это можно испортить). Это нужно смотреть.