Использование std:: function и std:: bind для хранения обратного вызова и удаления объектов.

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

поэтому я подумал о следующем: магазин a std::weak_ptr к объекту, а также std::function члену.

следующий кажется работа:

class MyBase {
public:
    MyBase() {}
    virtual ~MyBase() {}
};
//--------------------------------------------------

class MyClass : public MyBase {
public:
    MyClass() : MyBase() {}
    void myDouble(double val) const { std::cout << "Value is: " << val << std::endl; }
};
//--------------------------------------------------

Class Manager {
public:
    void setFunction(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
        m_function.first  = base;
        m_function.second = func;
    }
private:
    std::pair<std::weak_ptr<MyBase>, std::function<void(double)>> m_function;
};

использовать этот:

Manager db;
std::shared_ptr<MyClass> myClass = std::make_shared<MyClass>();
db.setFunction(myClass, std::bind(&MyClass::myDouble, myClass, std::placeholders::_1));

теперь я хочу скрыть std::bind часть от пользователя, так что ему нужно только позвонить:

db.setFunction(myClass, &MyClass::myDouble);

поэтому я хочу получить почти следующую работу в моей функции менеджера:

void setFunc2(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
    m_function.first  = base;
    m_function.second = std::bind(func, base, std::placeholders::_1);
}

но выше приведены ошибки:

error: no match for 'operator=' (operand types are 'std::function<void(double)>' and 
'std::_Bind_helper<false, std::function<void(double)>&, std::weak_ptr<MyBase>&, const std::_Placeholder<1>&>::type {aka std::_Bind<std::function<void(double)>(std::weak_ptr<MyBase>, std::_Placeholder<1>)>}')
         m_function.second = std::bind(func, base, std::placeholders::_1);

есть ли лучший способ сделать это, или, возможно, способ получить эту работу?

кое-что интересное, что я заметил. Если я использую std::shared_ptr в use_count() увеличивается с призывом std::bind в исходном коде. Таким образом, я не могу вручную сбросить/уничтожить объект, если я не отключу член на моем менеджере. Где это поведение документировано, я обычно использую cppreference?

Я посмотрел на следующий вопрос, но не могу заставить его работать для моей проблемы:как я могу использовать полиморфизм с функцией std::?

3 ответов


шаблон setFunction чтобы вы могли принимать указатель на производный член и не писать 12 перегрузок для комбинаций квалификаторов cv/ref.

template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
    // optionally static_assert that D2 is a base of D.
    m_function.first  = sp;
    m_function.second = std::bind(member, sp.get(), std::placeholders::_1);
}

очевидно, вам нужно убедиться, что вы lock() m_function.first перед вызовом m_function.second.

альтернативно, просто используйте лямбда, которая захватывает оба weak_ptr и указатель функции-члена:

std::function<void(double)> m_function;

template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
    std::weak_ptr<D> wp = sp;
    m_function = [wp, member](double d) {
        if(auto sp = wp.lock()){
             ((*sp).*member)(d);
        }
        else {
             // handle pointer no longer valid case.
        }
    };
}

мне нравится отделять моего слушателя / вещателя от реализации слушателя.

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

самый простой метод, который я нашел, - это заставить вещателя вернуть токен, время жизни которого определяет время жизни соединения.

using token = std::shared_ptr<void>;

template<class...Args>
struct broadcaster {
  using target = std::function<void(Args...)>;
  using wp_target = std::weak_ptr<target>;
  using sp_target = std::shared_ptr<target>;
  static sp_target wrap_target( target t ) {
    return std::make_shared<target>(std::move(t));
  };

  token start_to_listen( target f ) {
    auto t = wrap_target(std::move(f));
    targets.push_back(t);
    return t;
  }
  void broadcast( Args... args ) {
    targets.erase(
      std::remove_if( targets.begin(), targets.end(),
        [&]( wp_target t )->bool { return t.lock(); }
      ),
      targets.end()
    );
    auto targets_copy = targets; // in case targets is modified by listeners
    for (auto wp : targets_copy) {
      if (auto sp = wp.lock()) {
        (*sp)(args...);
      }
    }
  }
  std::vector<wp_target> targets;
};

это заставляет людей, которые регистрируют слушателей держать std::shared_ptr<void> вокруг.

мы можем даже сделать его более причудливым, где уничтожение последнего shared_ptr<void> фактически немедленно удаляет слушателя из списка. Но вышеупомянутое ленивое снятие с регистрации, кажется, работает достаточно хорошо в моем опыте, и относительно легко сделать его многопоточным. (одна серьезная проблема заключается в том, что происходит, когда широковещательное событие удаляет или добавляет вещи в список слушателей: адаптация вышеизложенного для его работы приятна и проста с правилом, что слушатели, добавленные при трансляции, не получают трансляция, и слушатели, удаленные во время трансляции, не получают трансляцию. Слушатели удалены по совместительству во время трансляции можно получить трансляцию в большинстве моих реализаций... Это становится дорогим, чтобы избежать.)


вместо этого мы могли бы разделить его по-другому. Слушатель может пройти std::function и a std::weak_ptr отдельно к вещателю, который хранит оба и только вызывает std::function Если std::weak_ptr действителен.


Мне нравится подход якка. Вот обновленная версия, которая исправляет несколько проблем компиляции (например, не может назвать функцию "register"). Он также добавляет метод rm_callback для клиентов, чтобы легко удалить их регистрацию, не заставляя их регистрационный маркер выходить за рамки или знать внутренние. Мне не нравилось сканировать список каждый раз, когда событие транслировалось, поэтому я добавил deleter на общий указатель, который выполняет задачу очистки. Все новые ошибки или недостатки-мои. Этот Alert reader должен знать о проблемах с потоками при изменении списка во время трансляции...

using token = std::shared_ptr<void>;
template<class...Args>
struct broadcaster {
    using target = std::function<void(Args...)>;
    using wp_target = std::weak_ptr<target>;
    using sp_target = std::shared_ptr<target>;

    token add_callback(target f) {
        sp_target t(new target(std::move(f)), [&](target*obj) { delete obj; cleanup(); });
        targets.push_back(t);
        return t;
    }

    static void rm_callback(token& t)
    {
        t.reset();
    }

    void cleanup()
    {
        targets.erase(
            std::remove_if(targets.begin(), targets.end(),
                [](wp_target t) { return t.expired(); }
            ),
            targets.end()
        );
    }

    void broadcast(Args... args) {
        for (auto wp : targets) {
            if (auto sp = wp.lock()) {
                (*sp)(args...);
            }
        }
    }

    std::vector<wp_target> targets;
};

// declare event taking a string arg
broadcaster<std::string> myEvent;