Играюсь с c++11 и появилась идея модернизировать асинхронные вызовы в своем проекте с использованием лямбд. Проблема в следующем: когда делаем асинхронный запрос и в качестве функции обратного вызова указываем лямбду, которая меняет состояние вызывающего объекта, к тому моменту, когда будет ответ готов, вызывающий объект может быть удален. Пример который это демонстрирует:
#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <memory>
#include <algorithm>
// Имитатор асинхронного объекта
class object
{
public:
typedef std::function<void(const std::string&) > callback;
// Асинхронный метод
virtual void async_method(const std::string& str, callback&& clb)
{
// Запоминаем аргумент и функцию обратного вызова
calls.push_back( std::make_pair(str, std::move(clb) ) );
}
// Выполняем все запрос пачкой
void doit()
{
for (auto& c : calls)
{
std::reverse(c.first.begin(), c.first.end() );
c.second(c.first);
}
calls.clear();
}
private:
std::vector< std::pair<std::string, callback> > calls;
};
// Имитатор вызывающего объекта
class subject
{
public:
subject(object* obj)
: _obj(obj)
{
}
void method(const std::string& str)
{
// вызываем асинхронный метод
_obj->async_method(str, [this](const std::string& str)
{
// ответ готов, вызываем метод который меняет состояние объекта
this->ready(str);
});
}
void ready(const std::string& str)
{
// для примера просто выводим строку
std::cout << "ready " << str << std::endl;
}
private:
object* _obj;
};
int main(int argc, char **argv)
{
object* obj = new object;
subject* s1 = new subject(obj);ю
subject* s2 = new subject(obj);
s1->method("1234");
s2->method("5678");
// удаляем один из вызывающих объектов
delete s1;
// выполняем все запросы
obj->doit();
delete obj;
delete s2;
return 0;
}
Т.к. у вызывающего объекта нет состояния этот пример сработает и выведет:
ready 4321
ready 8765
но по идее должен свалиться при выводе первой строки (если бы имел состояние), т.к. объект s1 был удален.
Решение пришло следующее (на умных указателях):
class subject
{
public:
subject(object* obj)
: _obj(obj)
, _pthis( std::make_shared<subject*>(this) ) // subject**
{
}
void method(const std::string& str)
{
std::weak_ptr<subject*> wthis = _pthis;
_obj->async_method(str, [wthis](const std::string& str)
{
// если объект был удален, то условие не сработает
if (auto pthis = wthis.lock())
{
(*pthis)->ready(str);
}
});
}
void ready(const std::string& str)
{
std::cout << " ready " << str << std::endl;
}
private:
object* _obj;
std::shared_ptr<subject*> _pthis;
};
Вопросы:
1. Есть-ли тут подводные камни?
2. Есть-ли стандартный кейс решения этой проблемы? ткните носом плз
Здравствуйте, laphroaig, Вы писали:
L>Вопросы:
L>1. Есть-ли тут подводные камни?
В твоей реализации shared_ptr не имеет никакого отношения к объекту subject. Это просто разделяемый
указатель на subject, а не разделяемый subject.
L>2. Есть-ли стандартный кейс решения этой проблемы? ткните носом плз
Если нужно shared владение, либо лень — тогда смотри
std::enable_shared_from_this,
например.
Если же move допустим, то что-то типа:
_obj->async_method(str, std::bind([](subject &self, const std::string& str)
{
self->ready(str);
}, std::move(*this), _1));
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Если нужно shared владение, либо лень — тогда смотри std::enable_shared_from_this, например.
Другой вариант — кладёшь все данные в отдельную скрытую структуру:
struct Subject
{
struct Inner {/*...*/};
shared_ptr<Inner> data;
// ...
};
тогда при передаче handler'а просто делаешь копию *this.
Здравствуйте, laphroaig, Вы писали:
L>Играюсь с c++11 и появилась идея модернизировать асинхронные вызовы в своем проекте с использованием лямбд. Проблема в следующем: когда делаем асинхронный запрос и в качестве функции обратного вызова указываем лямбду, которая меняет состояние вызывающего объекта, к тому моменту, когда будет ответ готов, вызывающий объект может быть удален. Пример который это демонстрирует:
Знакомая проблема.
Использую похожий вариант
class Subject
{
std::shared_ptr<int> m_LifeLine;
Subject():
m_LifeLine(new int(0xDEADBEEF))
{
}
///
void InitiateAsyncCall(object* _obj)
{
std::weak_ptr<int> Indicator(m_LifeLine);
_obj->DoAsync([this, Indicator]{
if (!_Indicator.expired())
{
//We are still alive. Proceed
this->SomeMethod();
}
else
{
// RIP
}
})
}
}