Я просмотрел enable_shared_from_this
при чтении примеров Boost.Asio, и после прочтения документации я все еще теряюсь за то, как это должно быть правильно использовано. Может кто-то, пожалуйста, дайте мне пример и/или объяснение, когда использование этого класса имеет смысл.
В чем польза `enable_shared_from_this`?
Ответ 1
Это позволяет вам получить действительный экземпляр 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. Вы также можете получить его оттуда, а также от повышения.
Ответ 2
из статьи доктора Доббса о слабых указателях, я думаю, что этот пример легче понять (источник: 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
}
Ответ 3
Здесь мое объяснение, с точки зрения орехов и болтов (верхний ответ не был 'click' со мной). * Обратите внимание, что это результат исследования источника для 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 * internal weak_ptr инициализируется с помощью refcount 1. Новый shared_ptr
в основном поддерживает этот weak_ptr
.
T
может затем в своих методах вызвать shared_from_this
, чтобы получить экземпляр shared_ptr<T>
, который поддерживает один и тот же внутренний счетчик ссылок. Таким образом, у вас всегда есть место, где хранится ref-count T*
, а не несколько экземпляров shared_ptr
, которые не знают друг о друге, и каждый считает, что они являются shared_ptr
, которые отвечают за ref- считая T
и удаляя его, когда их ref-count достигает нуля.
Ответ 4
Обратите внимание, что использование boost:: intrusive_ptr не страдает от этой проблемы. Это часто более удобный способ обойти эту проблему.
Ответ 5
Точно так же в С++ 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;
};
Ответ 6
Другой способ - добавить элемент weak_ptr<Y> m_stub
в class Y
. Затем напишите:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Полезно, если вы не можете изменить класс, из которого вы извлекаете (например, распространяете библиотеку других людей). Не забудьте инициализировать элемент, например. by m_stub = shared_ptr<Y>(this)
, то он действителен даже во время конструктора.
Это нормально, если в иерархии наследования есть больше заглушек, подобных этому, это не предотвратит уничтожение объекта.
Изменить: Как правильно указал пользователь nobar, код уничтожит объект Y, когда назначение будет завершено, а временные переменные будут уничтожены. Поэтому мой ответ неверен.