Я наткнулся на enable_shared_from_this
, читая примеры Boost.Asio, и после прочтения документации я все еще не понимаю, как это следует правильно использовать. Может ли кто-нибудь дать мне пример и объяснение того, когда использование этого класса имеет смысл.
Какая польза от `enable_shared_from_this`?
Ответы (6)
Это позволяет вам получить действительный экземпляр 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 для поддержки этого.
- person Matthew; 10.10.2017
std::shared_ptr
для объекта, которым уже управляет другой std::shared_ptr
, не будет обращаться к внутренней сохраненной слабой ссылке и, таким образом, приведет к неопределенному поведению. (en.cppreference.com/w/cpp/memory/enable_shared_from_this)
- person Thorbjørn Lindeijer; 01.05.2018
shared_ptr<Y> q = p
?
- person Dan M.; 08.05.2018
q
и вам нужен p
изнутри класса.
- person Hatted Rooster; 05.01.2019
std::make_shared<T>
.
- person Matthew; 07.03.2019
из статьи доктора Доббса о слабых указателях, я думаю, что этот пример легче понять (источник: 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/
- person Arun; 01.04.2015
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, это вызовет ошибку.
- person AnorZaken; 01.09.2016
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()
, потому что вам нужно знать, был ли он уже опубликован или нет.
- person AnorZaken; 01.09.2016
Вот мое объяснение с точки зрения гаек и болтов (верхний ответ мне не понравился). * Обратите внимание, что это результат исследования источника 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 *, этот внутренний weak_ptr T * инициализируется с refcount, равным 1. Новый shared_ptr
в основном возвращается к этому weak_ptr
.
Затем T
может в своих методах вызывать shared_from_this
для получения экземпляра shared_ptr<T>
, который обращается к тому же внутреннему сохраненному счетчику ссылок. Таким образом, у вас всегда будет одно место, где хранится T*
счетчик ссылок, а не несколько shared_ptr
экземпляров, которые не знают друг о друге, и каждый думает, что он shared_ptr
, который отвечает за счет T
ссылок и его удаление, когда их счетчик ссылок достигает нуля.
So, when you first create...
, потому что это требование (как вы говорите, weak_ptr не инициализируется до тех пор, пока вы не передадите указатель объектов в объект shared_ptr!), И это требование заключается в том, где все может пойти ужасно неправильно, если вы не будете осторожны. Если вы не создадите shared_ptr перед вызовом shared_from_this
, вы получите UB - аналогично, если вы создадите более одного shared_ptr, вы также получите UB. Вы должны каким-то образом убедиться, что вы создали shared_ptr ровно один раз.
- person AnorZaken; 01.09.2016
enable_shared_from_this
является хрупкой с самого начала, поскольку суть в том, чтобы иметь возможность получить shared_ptr<T>
от T*
, но на самом деле, когда вы получаете указатель T* t
, обычно небезопасно предполагать что-либо о том, что он уже используется или нет, а ошибочное предположение - UB.
- person AnorZaken; 01.09.2016
Есть один случай, когда я нахожу enable_shared_from_this чрезвычайно полезным: Безопасность потоков при использовании асинхронного обратного вызова.
Представьте, что класс Client имеет член типа AsynchronousPeriodicTimer:
struct AsynchronousPeriodicTimer
{
// call this periodically on some thread...
void SetCallback(std::function<void(void)> callback);
void ClearCallback(); // clears the callback
}
struct Client
{
Client(std::shared_ptr< AsynchronousPeriodicTimer> timer)
: _timer(timer)
{
_timer->SetCallback(
[this]
()
{
assert(this); // what if 'this' is already dead because ~Client() has been called?
std::cout << ++_counter << '\n';
}
);
}
~Client()
{
// clearing the callback is not in sync with the timer, and can actually occur while the callback code is running
_timer->ClearCallback();
}
int _counter = 0;
std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}
int main()
{
auto timer = std::make_shared<AsynchronousPeriodicTimer>();
{
auto client = std::make_shared<Client>(timer);
// .. some code
// client dies here, there is a race between the client callback and the client destructor
}
}
Клиентский класс подписывает функцию обратного вызова на периодический таймер. Как только клиентский объект выходит за пределы области видимости, возникает состояние гонки между обратным вызовом и деструктором. Обратный вызов может быть вызван с помощью висячего указателя!
Решение: используйте enable_shared_from_this:
struct Client : std::enable_shared_from_this<Client>
{
Client(std::shared_ptr< AsynchronousPeriodicTimer> timer)
: _timer(timer)
{
}
void Init()
{
auto captured_self = weak_from_this(); // weak_ptr to avoid cyclic references with shared_ptr
_timer->SetCallback(
[captured_self]
()
{
if (auto self = captured_self.lock())
{
// 'this' is guaranteed to be non-nullptr. we managed to promote captured_self to a shared_ptr
std::cout << ++self->_counter << '\n';
}
}
);
}
~Client()
{
_timer->ClearCallback();
}
int _counter = 0;
std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}
Метод Init
отделен от конструктора, поскольку процесс инициализации enable_shared_from_this
не завершается до выхода из конструктора. Отсюда дополнительный метод. Как правило, подписываться на асинхронный обратный вызов из конструктора небезопасно, поскольку обратный вызов может обращаться к неинициализированным полям.
Обратите внимание, что использование boost :: intrusive_ptr не страдает от этой проблемы. Часто это более удобный способ обойти эту проблему.
enable_shared_from_this
позволяет вам работать с API, который специально принимает shared_ptr<>
. На мой взгляд, таким API обычно является Doing It Wrong (так как лучше позволить чему-то более высокому в стеке владеть памятью), но если вы вынуждены работать с таким API, это хороший вариант.
- person cdunn2001; 05.06.2013
Точно так же в 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
. Возможно, вы захотите изменить интерфейс, чтобы убедиться, что это так.
- person curiousguy; 29.11.2017
std::shared_ptr<Node> getParent const()
я обычно выставляю его как NodePtr getParent const()
. Если вам абсолютно необходим доступ к внутреннему необработанному указателю (лучший пример: работа с библиотекой C), для этого есть std::shared_ptr<T>::get
, о чем я ненавижу упоминать, потому что этот аксессор необработанного указателя использовался слишком много раз по неправильной причине.
- person mchiasson; 10.12.2017