Различия shared_ptr и weak_ptr
Я читаю книгу Скотта Мейерса "Эффективный С++". Было упомянуто, что tr1::shared_ptr
и tr1::weak_ptr
действуют как встроенные указатели, но они отслеживают, сколько tr1::shared_ptrs
указывает на объект.
Это называется подсчетом ссылок. Это хорошо работает в предотвращении утечек ресурсов в ациклических структурах данных, но если два или более объекта содержат tr1::shared_ptrs
, так что цикл формируется, цикл может поддерживать счетчик ссылок выше нуля, даже если все внешние указатели на цикл были уничтожены.
Что там, где tr1::weak_ptrs
.
Мой вопрос в том, как циклические структуры данных делают число ссылок выше нуля. Я прошу пример программы на С++. Как решить проблему с помощью weak_ptrs
? (опять же, пожалуйста, пример).
Ответы
Ответ 1
A shared_ptr
завершает механизм подсчета ссылок вокруг необработанного указателя. Поэтому для каждого экземпляра shared_ptr
счетчик ссылок увеличивается на единицу. Если два объекта share_ptr
ссылаются друг на друга, они никогда не будут удалены, потому что они никогда не будут иметь счетчик ссылок с нулем.
weak_ptr
указывает на shared_ptr
, но не увеличивает счетчик ссылок. Это означает, что объект-подделку можно удалить, даже если есть ссылка weak_ptr
.
Способ, которым это работает, заключается в том, что weak_ptr
может использоваться для создания shared_ptr
для всякий раз, когда вы хотите использовать базовый объект. Если, однако, объект уже удален, возвращается пустой экземпляр shared_ptr
. Поскольку счетчик ссылок на базовый объект не увеличивается с помощью ссылки weak_ptr
, круговая ссылка не приведет к тому, что основной объект не будет удален.
Ответ 2
Позвольте мне повторить ваш вопрос: "Мой вопрос, как циклические структуры данных делают ссылочный счет выше нуля, любезно запросите показать пример с помощью программы на С++. Как проблема решается с помощью weak_ptrs
снова с примером пожалуйста".
Проблема возникает с таким кодом С++ (концептуально):
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
) не может использоваться для решения проблемы цикла - программист решает проблему цикла.
Чтобы решить эту проблему, программист должен знать связь собственности между объектами или должен изобретать отношения собственности, если такое владение не существует естественным образом.
Вышеупомянутый код С++ может быть изменен так, что 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, С#, Go, Haskell) или использование консервативного сборщика мусора (= несовершенного), который работает с C/С++ (например, Boehm GC).
Ответ 3
Для будущих читателей.
Просто хочу указать, что объяснение, данное 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;
Ответ 4
Слабые указатели просто "наблюдают" управляемый объект; они не "поддерживают его" или влияют на его жизнь. В отличие от shared_ptr
, когда последний weak_ptr
выходит за пределы области видимости или исчезает, объект, указывающий на объект, все еще может существовать, потому что weak_ptr
не влияет на время жизни объекта - он не имеет прав собственности. weak_ptr
может использоваться для определения того, существует ли объект и предоставить shared_ptr
, который может использоваться для его ссылки.
Определение weak_ptr
предназначено для того, чтобы сделать его относительно надежным, поэтому в результате очень мало можно сделать непосредственно с weak_ptr
. Например, вы не можете разыгрывать его; ни operator*
, ни operator->
не определено
для a weak_ptr
. Вы не можете получить доступ к указателю на объект с ним - нет функции get()
. Существует функция сравнения, которая позволяет хранить weak_ptrs
в упорядоченном контейнере, но все.
Ответ 5
Все приведенные выше ответы НЕПРАВИЛЬНЫ. 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
, хранящийся в контейнере, не истек, прежде чем использовать его.