Подтвердить что ты не робот

Различия shared_ptr и weak_ptr

Я читаю книгу Скотта Мейерса "Эффективный С++". Было упомянуто, что tr1::shared_ptr и tr1::weak_ptr действуют как встроенные указатели, но они отслеживают, сколько tr1::shared_ptrs указывает на объект.

Это называется подсчетом ссылок. Это хорошо работает в предотвращении утечек ресурсов в ациклических структурах данных, но если два или более объекта содержат tr1::shared_ptrs, так что цикл формируется, цикл может поддерживать счетчик ссылок выше нуля, даже если все внешние указатели на цикл были уничтожены.

Что там, где tr1::weak_ptrs.

Мой вопрос в том, как циклические структуры данных делают число ссылок выше нуля. Я прошу пример программы на С++. Как решить проблему с помощью weak_ptrs? (опять же, пожалуйста, пример).

4b9b3361

Ответ 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, хранящийся в контейнере, не истек, прежде чем использовать его.