callback лямбда и удаление целевого объекта
От: laphroaig  
Дата: 19.11.13 20:46
Оценка:
Играюсь с 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. Есть-ли стандартный кейс решения этой проблемы? ткните носом плз
с++11 lambda callback delete this
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.