Ответы:
Это позволяет вам получить действительный 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. Вы также можете получить его оттуда, а также от повышения.
std::shared_ptr
конструктор на сырой указатель , если он наследует от std::enable_shared_from_this
. Я не знаю, была ли обновлена семантика Boost для поддержки этого.
std::shared_ptr
объекта для объекта, который уже управляется другим std::shared_ptr
, не будет обращаться к внутренне хранимой слабой ссылке и, следовательно, приведет к неопределенному поведению». ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p
?
std::make_shared<T>
.
из статьи доктора Доббса о слабых указателях, я думаю, этот пример легче понять (источник: 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
}
shared_ptr<S> sp1(new S);
чтобы использовать его shared_ptr<S> sp1 = make_shared<S>();
, см., Например, stackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();
потому что подводный камень в том, что вы должны создать shared_ptr обычным способом, прежде чем вызывать shared_from_this()
в первый раз! Это действительно легко ошибиться! До C ++ 17 это UB для вызова shared_from_this()
до того, как ровно один shared_ptr был создан обычным способом: auto sptr = std::make_shared<S>();
или shared_ptr<S> sptr(new S());
. К счастью, начиная с C ++ 17 и далее, будет выбрасывать.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();
<- разрешено вызывать shared_from_this только для ранее общего объекта, то есть для объекта, управляемого std :: shared_ptr <T>. В противном случае поведение не определено (до C ++ 17), генерируется std :: bad_weak_ptr (с помощью конструктора shared_ptr, созданного по умолчанию weak_this) (начиная с C ++ 17). , Таким образом, реальность такова, что его следует называть always_dangerous()
, потому что вам нужно знание, было ли оно уже передано или нет.
Вот мое объяснение с точки зрения гайки и болты (верхний ответ не «щелкнул» со мной). * Обратите внимание, что это результат изучения источника для 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>
тот же внутренний счетчик ссылок . Таким образом, у вас всегда есть одно место, где T*
хранится ref-count, а не несколько shared_ptr
экземпляров, которые не знают друг о друге, и каждый думает, shared_ptr
что именно он отвечает за подсчет T
и удаление его, когда их ref Счет достигает нуля.
So, when you first create...
том, что это требование (как вы говорите, weak_ptr не инициализируется до тех пор, пока вы не передадите указатель объектов в ctor shared_ptr!), И это требование - то, где все может пойти не так, если вы не осторожно Если вы не создали shared_ptr перед вызовом, shared_from_this
вы получите UB - аналогично, если вы создадите более одного shared_ptr, вы получите и UB. Вы должны как-то убедиться, что вы создали shared_ptr ровно один раз.
enable_shared_from_this
хрупка с самого начала, так как смысл в том, чтобы иметь возможность получить a shared_ptr<T>
от a T*
, но на самом деле, когда вы получаете указатель, T* t
обычно небезопасно предполагать, что что-то о нем уже передано или нет, и сделать неправильное предположение UB.
Обратите внимание, что использование boost :: intrusive_ptr не страдает от этой проблемы. Часто это более удобный способ обойти эту проблему.
enable_shared_from_this
позволяет работать с API, который специально принимает shared_ptr<>
. На мой взгляд, такой API, как правило, делает неправильно, так как лучше, чтобы в стеке было что-то более высокое, но если вы вынуждены работать с таким API, это хороший вариант.
Это точно так же в 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;
};
shared_ptr
. Возможно, вы захотите изменить интерфейс, чтобы убедиться, что это так.
std::shared_ptr<Node> getParent const()
, чтобы я обычно выставлял это как NodePtr getParent const()
вместо. Если вам абсолютно необходим доступ к внутреннему необработанному указателю (лучший пример: работа с библиотекой C), то есть std::shared_ptr<T>::get
то, о чем я не хочу упоминать, потому что я использовал этот обработчик необработанных указателей слишком много раз по неправильной причине.
Другой способ - добавить weak_ptr<Y> m_stub
участника в class Y
. Затем написать:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Полезно, когда вы не можете изменить класс, из которого вы производите (например, расширение библиотеки других людей). Не забудьте инициализировать элемент, например, с помощью m_stub = shared_ptr<Y>(this)
, он действителен даже во время конструктора.
Это нормально, если в иерархии наследования есть больше таких заглушек, как эта, это не предотвратит уничтожение объекта.
Редактировать: как правильно указал пользователь nobar, код уничтожит объект Y, когда присваивание будет завершено, а временные переменные уничтожены. Поэтому мой ответ неверен.
shared_ptr<>
который не удаляет его pointee, это излишне. Вы можете просто сказать, return shared_ptr<Y>(this, no_op_deleter);
где no_op_deleter
унарный функциональный объект берет Y*
и ничего не делает.
m_stub = shared_ptr<Y>(this)
построит и немедленно уничтожит временный shared_ptr из этого. Когда это утверждение закончится, this
будут удалены и все последующие ссылки будут болтаться.
enable_shared_from_this
, она сохраняет weak_ptr
себя (заполняется ctor), возвращаемую как shared_ptr
при вызове shared_from_this
. Другими словами, вы дублируете то, что enable_shared_from_this
уже обеспечивает.