Вот простой вещатель C++:
using token = std::shared_ptr<void>;
template<class...Ts>
struct broadcaster {
using listen = std::function<void(Ts...)>;
using sp_listen = std::shared_ptr<listen>;
using wp_listen = std::weak_ptr<listen>;
token attach( listen l ) {
return attach( std::make_shared<listen>(std::move(l)) );
}
token attach( sp_listen sp ) {
listeners.push_back(sp);
return sp;
}
void operator()(Ts...ts)const {
listeners.erase(
std::remove_if( begin(listeners), end(listeners),
[](auto&& wp){return !(bool)wp.lock();}
),
end(listeners)
);
auto tmp = listeners;
for (auto&& l : tmp) {
if (auto pf = l.lock()) {
(*pf)(ts...);
}
}
}
private:
mutable std::vector<wp_listen> listeners;
};
Чтобы прослушать его, вы .attach
передаете ему функцию для вызова. attach
возвращает token
, и функция вызывается до тех пор, пока этот токен (или его копии) продолжает существовать.
Чтобы вызвать сообщение, вы вызываете ()
в файле broadcaster
.
Память мертвых обратных вызовов восстанавливается при следующем вызове вещателя; косвенно принадлежащие ресурсы очищаются быстрее.
Если вы зарегистрируете прослушиватель во время его текущей трансляции, он не получит текущую трансляцию.
Вы можете добавить std::mutex
, чтобы это можно было использовать сразу из нескольких потоков, или синхронизировать извне. Если вы синхронизируетесь внутри, я бы не стал удерживать мьютекс, когда вы запускаете цикл for(auto&&
в ()
, чтобы избежать проблем с повторным входом.
Пример использования:
struct location {
int x, y;
};
struct button {
broadcaster< location > mouse_click;
broadcaster<> mouse_enter;
broadcaster<> mouse_leave;
};
struct dancer {
std::vector<token> listen_tokens;
dancer( button& b ) {
listen_tokens.push_back( b.mouse_enter.attach([this]{ dance(); } ) );
listen_tokens.push_back( b.mouse_leave.attach([this]{ end_dance(); } ) );
listen_tokens.push_back( b.mouse_click.attach(
[this](location l){
pose(l.x, l.y);
}
) );
}
void dance() const {
std::cout << "start dancing\n";
}
void pose( int x, int y ) const {
std::cout << "struck a pose at " << x << ", " << y << "\n";
}
void end_dance() const {
std::cout << "end dancing\n";
}
};
Обратите внимание, что virtual
методы не использовались. Единственным полиморфизмом было стирание типа на основе std::function
.
Объекты прослушивания должны отслеживать время жизни, в течение которого они явно прослушивают (поддерживая token
активным), и если они хотят иметь возможность отменить регистрацию на определенном вещателе, они должны сами поддерживать эту ассоциацию.
Если вещательные компании уйдут первыми, проблем не будет. Если слушатели уходят, пока их токен предшествует им, все в порядке. Присоединение вызывает динамическое выделение для хранения токена (и перемещения в копию слушателя), но только одного.
Это другой подход, чем вы использовали бы в C#, потому что он опирается на RAII и потому что он не является объектно-ориентированным по своей сути, но остается полиморфным.
person
Yakk - Adam Nevraumont
schedule
02.01.2018
std::vector<std::any> const& params
. - person Christian Hackl   schedule 01.01.2018std::any
стал доступен в C++17, см. en.cppreference.com/w /cpp/utility/any и примеры на en.cppreference. com/w/cpp/utility/any/any_cast - person Christian Hackl   schedule 01.01.2018std::function
, и это, кажется, не имеет ничего общего с хранением какой-либо подписи. Я понятия не имею, почему вы хотите хранить какие-либо подписи; вы хотите, чтобы типы вызванных подписей соответствовали типам подписей, зарегистрированных, и вы хотите, чтобы это проверялось во время компиляции, если вы в здравом уме. - person Yakk - Adam Nevraumont   schedule 02.01.2018