Различия shared_ptr и weak_ptr - PullRequest
       40

Различия shared_ptr и weak_ptr

68 голосов
/ 13 февраля 2011

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

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

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

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

Ответы [ 5 ]

107 голосов
/ 19 сентября 2011

Позвольте мне повторить ваш вопрос: «Мой вопрос, как циклические структуры данных делают счетчик ссылок выше нуля, просьба показать с примером в программе на 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) .

46 голосов
/ 13 февраля 2011

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

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

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

17 голосов
/ 20 сентября 2013

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

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

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

0 голосов
/ 10 ноября 2011

Все вышеприведенные ответы НЕПРАВИЛЬНЫ.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, хранящийся в контейнере, не истек до его использования.

...