В чем полезность `enable_shared_from_this`? - PullRequest
312 голосов
/ 03 апреля 2009

Я столкнулся с enable_shared_from_this во время чтения примеров Boost.Asio и после прочтения документации, я все еще не понял, как это правильно использовать. Может кто-нибудь дать мне пример и / или объяснение, когда использование этого класса имеет смысл.

Ответы [ 6 ]

324 голосов
/ 03 апреля 2009

Позволяет получить действительный экземпляр shared_ptr для this, когда все, что у вас есть, - this. Без него у вас не было бы возможности получить от shared_ptr до this, если бы у вас его еще не было. Этот пример из расширенной документации для enable_shared_from_this :

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

Метод f () возвращает допустимый shared_ptr, даже если у него нет экземпляра члена. Обратите внимание, что вы не можете просто сделать это:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}

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

enable_shared_from_this стал частью стандарта C ++ 11. Вы также можете получить его оттуда и от наддува.

179 голосов
/ 05 апреля 2011

из статьи доктора Доббса о слабых указателях, я думаю, этот пример легче понять (источник: http://drdobbs.com/cpp/184402026):

... код, подобный этому, не будет работать правильно:

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

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

Аналогично, если функции-члену необходим объект shared_ptr, которому принадлежит объект, к которому она вызывается, она не может просто создать объект на лету:

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

Этот код имеет ту же проблему, что и предыдущий пример, хотя и в более тонкой форме. Когда он создан, объект shared_pt r sp1 владеет вновь выделенным ресурсом. Код внутри функции-члена S::dangerous не знает об этом shared_ptr объекте, поэтому возвращаемый им объект shared_ptr отличается от sp1. Копирование нового объекта shared_ptr в sp2 не помогает; когда sp2 выходит из области действия, он освобождает ресурс, а когда sp1 выходит из области действия, он снова освобождает ресурс.

Чтобы избежать этой проблемы, используйте шаблон класса enable_shared_from_this. Шаблон принимает один аргумент типа шаблона, который является именем класса, который определяет управляемый ресурс. Этот класс, в свою очередь, должен быть открыт из шаблона; как это:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}

Когда вы делаете это, имейте в виду, что объект, для которого вы вызываете shared_from_this, должен принадлежать объекту shared_ptr. Это не сработает:

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}
27 голосов
/ 01 августа 2012

Вот мое объяснение с точки зрения гайки и болты (верхний ответ не «щелкнул» со мной). * Обратите внимание, что это результат исследования источника для shared_ptr и enable_shared_from_this, который поставляется с Visual Studio 2012. Возможно, другие компиляторы реализуют enable_shared_from_this по-другому ... *

enable_shared_from_this<T> добавляет частный weak_ptr<T> экземпляр к T, который содержит ' один истинный счетчик ссылок ' для экземпляра T.

Таким образом, когда вы впервые создаете shared_ptr<T> для нового T *, внутренний слабый_птр этого T * инициализируется с повторным счетом 1. Новый shared_ptr в основном возвращается к этому weak_ptr.

T может затем в своих методах вызвать shared_from_this, чтобы получить экземпляр shared_ptr<T>, который возвращает на тот же внутренний счетчик ссылок . Таким образом, у вас всегда есть одно место, где хранится ref-count T*, а не несколько shared_ptr экземпляров, которые не знают друг о друге, и каждый думает, что это shared_ptr, который отвечает за ref-count T и удаление его, когда их ref-count достигает нуля.

3 голосов
/ 13 июня 2012

Обратите внимание, что использование boost :: intrusive_ptr не страдает от этой проблемы. Часто это более удобный способ обойти эту проблему.

2 голосов
/ 16 ноября 2017

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

другими словами, он позволяет вам поворачивать код следующим образом

class Node {
public:
    Node* getParent const() {
        if (m_parent) {
            return m_parent;
        } else {
            return this;
        }
    }

private:

    Node * m_parent = nullptr;
};           

в это:

class Node : std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getParent const() {
        std::shared_ptr<Node> parent = m_parent.lock();
        if (parent) {
            return parent;
        } else {
            return shared_from_this();
        }
    }

private:

    std::weak_ptr<Node> m_parent;
};           
0 голосов
/ 16 января 2011

Другой способ - добавить члена weak_ptr<Y> m_stub в class Y. Затем напишите:

shared_ptr<Y> Y::f()
{
    return m_stub.lock();
}

Полезно, когда вы не можете изменить класс, из которого вы производите (например, расширение библиотеки других людей). Не забудьте инициализировать член, например, на m_stub = shared_ptr<Y>(this) он действителен даже во время конструктора.

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

Редактировать: Как правильно указал пользователь nobar, код уничтожит объект Y, когда присвоение будет завершено, а временные переменные уничтожены. Поэтому мой ответ неверен.

...