удалить любой элемент вектора>, связанный с функцией-членом


Как удалить функцию, которая связана с функцией-членом объекта this:

std::vector<std::function<void(int)>> callbacks;

class MyClass {
public:
    MyClass() {
        callbacks.push_back(
            std::bind(&MyClass::myFunc,this,std::placeholders::_1)
        );
    }
    ~MyClass() {
        auto it = std::remove_if( std::begin(callbacks),
                                  std::end(callbacks),
                                  [&](std::function<void(int)>& f) {
                return // <-- this is my question
                       //     true (remove) if f is bound to member function 
                       //     of this
        });
        callbacks.erase(it,std::end(callbacks));
    }
    void myFunc(int param){...}
};
3 3

3 ответа:

В общем случае вы не можете обойтись без дополнительной работы. Тип erasure удаляет эту информацию из объекта, и std::function не предоставляет эту информацию напрямую.

Ваш конкретный пример может иметь только одну функцию-член, которая может быть кандидатом на удаление, но как насчет класса с 5 членами, которые могут храниться как обратные вызовы? Вам нужно будет протестировать их все, а также можно связать функции-члены с помощью лямбды, что довольно много необнаруживаемый.

Вот одно решение, если:

  • все обратные вызовы регистрируются изнутри MyClass
  • контейнер изменен для хранения дополнительной информации
  • вы готовы взять на себя всю дополнительную бухгалтерию
std::vector<std::pair<std::function<void(int)>, void*>> callbacks;

class MyClass{
  static unsigned const num_possible_callbacks = 2; // keep updated
  std::array<std::type_info const*, num_possible_callbacks> _infos;
  unsigned _next_info;

  // adds type_info and passes through
  template<class T>
  T const& add_info(T const& bound){
    if(_next_info == num_possible_callbacks)
      throw "oh shi...!"; // something went out of sync
    _infos[_next_info++] = &typeid(T);
    return bound;
  }
public:
  MyClass() : _next_info(0){
    using std::placeholders::_1;
    callbacks.push_back(std::make_pair(
        add_info(std::bind(&MyClass::myFunc, this, _1)),
        (void*)this));
    callbacks.push_back(std::make_pair(
        add_info([this](int i){ return myOtherFunc(i, 0.5); }),
        (void*)this));
  }

  ~MyClass(){
    using std::placeholders::_1;

    callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(),
        [&](std::pair<std::function<void(int)>, void*> const& p) -> bool{
          if(p.second != (void*)this)
            return false;
          auto const& f = p.first;
          for(unsigned i = 0; i < _infos.size(); ++i)
            if(_infos[i] == &f.target_type())
              return true;
          return false;
        }), callbacks.end());
  }

  void myFunc(int param){ /* ... */ }
  void myOtherFunc(int param1, double param2){ /* ... */ }
};

Живой пример на Ideone.

    typedef decltype(std::bind(&MyClass::myFunc,this,std::placeholders::_1)) bound_type;

    auto it = std::remove_if( std::begin(callbacks),
                              std::end(callbacks),
                              [](const std::function<void(int)>& f) {
          return f.target<bound_type>() != nullptr;
    });

Шаблон функции-члена std::function::target<T> возвращает указатель на целевой объект, если он имеет тип T, в противном случае он возвращает null. Поэтому вам просто нужно иметь возможность назвать тип целевого объекта, который вы можете получить из decltype. Довольно просто на самом деле: -)

N. B. который удалит любые обратные вызовы этого типа, а не только те, которые связали указатель this для конкретного уничтожаемого объекта. Если вы пытаетесь предотвратить вызов обратных вызовов для объекта после него если вы не можете определить, какие элементы вектора относятся к каким объектам, вы можете поместить shared_ptr в свой класс, а затем сохранить weak_ptr для него в обратном вызове, который может быть использован для обнаружения, если объект был уничтожен:

class MyClass
{
    struct NullDeleter { void operator()(void*) const { } };
    std::shared_ptr<MyClass> sp;

    static void safe_invoke(void (MyClass::*f)(int), const std::weak_ptr<MyClass>& wp, int i)
    {
        if (std::shared_ptr<MyClass> safe_this = wp.lock())
            (safe_this.get()->*f)(i);
    }

public:
    MyClass() : sp(this, NullDeleter()) {
        callbacks.push_back(
            std::bind(safe_invoke, &MyClass::myFunc ,std::weak_ptr<MyClass>(sp),
                      std::placeholders::_1)
        );
    };

Это оборачивает вызов функции-члена с функцией invoke, которая преобразует weak_ptr в shared_ptr перед вызовом функции-члена. Если объект был уничтожен, то shared_ptr будет пустым, так что функция ничего не делает. Это фактически не удаляет обратный вызов, когда он становится недействительным, но делает его безопасным для вызова.

Однажды мне нужно было сделать что-то подобное, и я решил это, сохранив вектор общих указателей объектов в классе, содержащих функцию, и удалив функцию из вектора по значению, когда они будут уничтожены, что также делает это автоматическим.