it-swarm-ru.tech

Различия shared_ptr и weak_ptr

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

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

Вот где приходит tr1::weak_ptrs.

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

66
venkysmarty

shared_ptr оборачивает механизм подсчета ссылок вокруг необработанного указателя. Таким образом, для каждого экземпляра shared_ptr счетчик ссылок увеличивается на единицу. Если два объекта share_ptr ссылаются друг на друга, они никогда не будут удалены, потому что у них никогда не будет нулевого счетчика ссылок.

weak_ptr указывает на shared_ptr, но не увеличивает его счетчик ссылок. Это означает, что базовый объект все еще может быть удален, даже если на него есть ссылка weak_ptr.

Это работает так, что weak_ptr можно использовать для создания shared_ptr всякий раз, когда кто-то хочет использовать базовый объект. Однако если объект уже был удален, возвращается пустой экземпляр shared_ptr. Поскольку счетчик ссылок на базовый объект не увеличивается с помощью ссылки weak_ptr, циклическая ссылка не приведет к тому, что базовый объект не будет удален.

46
doron

Позвольте мне повторить ваш вопрос: "Мой вопрос, как циклические структуры данных делают счетчик ссылок выше нуля, просьба показать его с примером в программе на C++. Как проблема решается с помощью weak_ptrs еще раз с примером, пожалуйста".

Проблема возникает с кодом C++ следующим образом (концептуально):

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)

Чтобы ответить на вторую часть вашего вопроса: для подсчета ссылок математически невозможно иметь дело с циклами. Следовательно, weak_ptr (который в основном является просто урезанной версией shared_ptr) не может может использоваться для решения проблемы цикла - программист решает проблему цикла.

Чтобы решить эту проблему, программист должен знать об отношениях владение между объектами или должен изобрести отношения владения, если такого владения не существует естественным образом.

Вышеупомянутый код C++ может быть изменен так, чтобы 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, C #, Go, Haskell) или использовать консервативный (= несовершенный) сборщик мусора, который работает с C/C++ (например, Boehm GC). ,.

106
user811773

Для будущих читателей.
Просто хочу отметить, что объяснение, данное 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;  
17
newprint

Слабые указатели просто "наблюдают" за управляемым объектом; они не "поддерживают его жизнь" и не влияют на его жизнь. В отличие от shared_ptr, когда последний weak_ptr выходит из области видимости или исчезает, указанный объект все еще может существовать, поскольку weak_ptr не влияет на время жизни объекта - он не имеет прав собственности. weak_ptr может использоваться для определения того, существует ли объект, и для предоставления shared_ptr, который можно использовать для ссылки на него.

Определение weak_ptr предназначено для того, чтобы сделать его относительно надежным, поэтому в результате очень мало что можно сделать напрямую с weak_ptr. Например, вы не можете разыменовать это; ни operator*, ни operator-> не определены для weak_ptr. Вы не можете получить доступ к указателю на объект с ним - нет функции get(). Существует определенная функция сравнения, позволяющая хранить weak_ptrs в упорядоченном контейнере, но это все.

4
peterDriscoll