Когда полезен std:: weak ptr?


Я начал изучать смарт-указатели C++11, и я не вижу никакого полезного использования std::weak_ptr. Может кто-нибудь сказать мне, когда std::weak_ptr полезно/нужно?

11 183

11 ответов:

хорошим примером может быть кэш.

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

но что, если этот объект используется и какой-то другой код содержит сильный указатель на него? Если кэш избавляется от своего единственного указателя на объект, он может никогда не найду его снова. Таким образом, кэш хранит слабый указатель на объекты, которые ему нужно найти, если они остаются в памяти.

Это именно то, что делает слабый указатель-он позволяет вам найти объект, если он все еще вокруг, но не держит его вокруг, если ничего больше не нужно.

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";
}

другой ответ, надеюсь, проще. (для коллег-гуглеров)

предположим, что у вас есть Team и Member объекты.

очевидно, что это отношения:Team "объект" будет иметь указатели на его Members. И вполне вероятно, что члены также будут иметь обратный указатель на их

вот один пример, данный мне @jleahy: Предположим, у вас есть коллекция задач, выполняемых асинхронно и управляемых std::shared_ptr<Task>. Вы можете сделать что-то с этими задачами периодически, так что событие таймера может пройти std::vector<std::weak_ptr<Task>> и дать задачам что-то делать. Однако одновременно задача может одновременно решить, что она больше не нужна и умереть. Таким образом, таймер может проверить, жива ли задача, сделав общий указатель из слабого указателя и используя этот общий указатель, если он не равен нулю.

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());

shared_ptr : имеет реальный объект.

weak_ptr использует lock для подключения к реальному владельцу или возвращает NULL в противном случае.

weak ptr

грубо говоря, weak_ptr роль похожа на роль жилищное агентство. Без агентов, чтобы получить дом в аренду, нам, возможно, придется проверить случайные дома в городе. Агенты удостоверяются, что мы посещаем только те дома, которые еще доступным и в аренду.

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

при использовании указателей, важно понять различные типы указателей и когда имеет смысл использовать каждый из них. Существует четыре типа указателей в двух категориях следующим образом:

  • сырые указатели:
    • Raw указатель [ т. е. 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 и освобождение выделенной памяти, как только объект, который "владеет" уникальным указателем, выходит из области видимости. Имя "уникальный" относится к тому факту, что только один объект может " владеть уникальный указатель в заданный момент времени. Право собственности может быть передано другому объекту с помощью команды move, но уникальный указатель никогда не может быть скопирован или совместно использован. По этим причинам уникальные указатели являются хорошей альтернативой необработанным указателям в том случае, если только один объект нуждается в указателе в данный момент времени, и это избавляет разработчика от необходимости освобождать память в конце жизненного цикла объекта-владельца.

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

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

  • вы заняты и имеете перекрывающиеся встречи: Встреча A и встреча B
  • вы решили пойти на встречу A и ваш коллега идет на встречу B
  • вы говорите своему коллеге, что если встреча B все еще продолжается после окончания встречи A, вы будете присоединяйтесь
  • следующие два сценария могут играть:
    • встреча a заканчивается, а встреча B все еще продолжается, поэтому вы присоединяетесь
    • встреча a заканчивается, и встреча B также закончилась, поэтому вы не присоединяетесь

в Примере у вас есть слабый указатель на встречу B. Вы не являетесь "владельцем" В встрече B, поэтому она может закончиться без вас, и вы не знаете, закончилась ли она или нет, если вы не проверите. Если он еще не закончился, вы можете присоединиться и принять участие, в противном случае, вы не можете. Это отличается от общего указателя на собрание B, потому что тогда вы будете "владельцем" как на собрании A, так и на собрании B (участвуя в обоих одновременно).

пример иллюстрирует, как работает слабый указатель и полезен, когда объект должен быть снаружи обозреватель, но не хочет ответственности собственника. Это особенно полезно в сценарии, когда два объекта должны указывать друг на друга (например, круговой ссылка.) С помощью общих указателей ни один из объектов не может быть освобожден, потому что они по-прежнему "сильно" указывают на другой объект. С помощью слабых указателей, объекты могут быть доступны, когда это необходимо, и освобождены, когда они больше не должны существовать.

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.

когда мы не хотим владеть объектом:

Ex:

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 выходит из области od, счетчик все еще остается 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, что имеет больше смысла.