Сохраните функцию с произвольными аргументами и заполнителями в классе и вызовите ее позже
Итак, я создаю тип обработчика событий и в процессе написания "Оболочки прослушивателя событий", если хотите.
Основная идея такова: когда вы хотите подписаться на событие, вы создаете функцию, которая должна вызываться при возникновении события. <- уже сделали это (вроде, я объясню)
Вы помещаете эту функцию слушателя в оболочку, чтобы передать функцию диспетчеру.
Диспетчер получает событие, находит оболочку для прослушивателя и вызывает базовую функцию со значениями параметров, установленными событием.
У меня уже что-то работает, пока все слушатели принимают только один аргумент моего EventBase
учебный класс. Затем я должен набрать приведение к соответствующему событию, что слушатель передается.
Вместо этого я хочу, чтобы мои функции слушателя имели аргументы "любого" типа и сохраняли функцию так, чтобы я мог вызывать ее с любыми аргументами, которые я хочу, в зависимости от инициируемого события. Каждая функция слушателя получит только один тип события или событие, которое оно само. Это позволило бы мне не вводить бросок каждое событие в каждом слушателе, но вместо этого было бы передано правильное событие.
Я нашел немного кода для этой обертки, который почти идеален, с несколькими незначительными проблемами, которые я не могу исправить. Я объясню ниже.
Код @hmjd:
#include <iostream>
#include <string>
#include <functional>
#include <memory>
void myFunc1(int arg1, float arg2)
{
std::cout << arg1 << ", " << arg2 << '\n';
}
void myFunc2(const char *arg1)
{
std::cout << arg1 << '\n';
}
class DelayedCaller
{
public:
template <typename TFunction, typename... TArgs>
static std::unique_ptr<DelayedCaller> setup(TFunction&& a_func,
TArgs&&... a_args)
{
return std::unique_ptr<DelayedCaller>(new DelayedCaller(
std::bind(std::forward<TFunction>(a_func),
std::forward<TArgs>(a_args)...)));
}
void call() const { func_(); }
private:
using func_type = std::function<void()>;
DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
func_type func_;
};
int main()
{
auto caller1(DelayedCaller::setup(&myFunc1, 123, 45.6));
auto caller2(DelayedCaller::setup(&myFunc2, "A string"));
caller1->call();
caller2->call();
return 0;
}
Первое, что я сделал здесь, я должен был заменить std::unique_ptr
с std::shared_ptr
, Не уверен, почему на самом деле. Это почти работает. В моем случае использования мне нужно сохранить функцию метода (что означает, что bind должен быть передан содержащий объект метода?), И во время сохранения функции я не знаю, каким будет значение аргумента, то есть для событие, чтобы решить. Итак, мои настройки следующие:
class DelayedCaller
{
public:
template <typename TFunction, typename TClass>
static std::shared_ptr<DelayedCaller> setup(TFunction&& a_func,
TClass && a_class)
{
auto func = std::bind(std::forward<TFunction>(a_func),
std::forward<TClass>(a_class),
std::placeholders::_1);
return std::shared_ptr<DelayedCaller>(new DelayedCaller(func));
}
template <typename T>
void call( T v ) const { func_(v); }
private:
using func_type = std::function<void( )>;
DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
func_type func_;
};
Ради тестирования я удалил пакет параметров и заменил его прямым параметром для объекта класса, содержащего функцию. Я также дал привязку заменитель для 1 аргумента (в идеале заменить void call()
функция позже).
Он создан так:
eventManager->subscribe(EventDemo::descriptor, DelayedCaller::setup(
&AppBaseLogic::getValueBasic,
this
));
Проблема в следующем:
return std::shared_ptr<DelayedCaller>(new DelayedCaller(func));
Я получаю "нет соответствующей функции для вызова 'DelayedCaller::DelayedCaller(std::_Bind(AppBaseLogic*, std::_Placeholder<1>)>&)' return std::shared_ptr(new DelayedCaller(func));"
Это происходит только при использовании placeholder::_1
, если я заменю это на известное значение правильного типа, это сработает, за исключением того, что функция вызывается без каких-либо полезных данных.
Итак, я думаю, мне нужен способ хранения функции с заполнителями, тип которых я не знаю?
Простите, если я неправильно понимаю названия вещей. Я очень новичок в C++, я только начал изучать его в последние несколько дней.
**Редактировать: **
Итак, я просто уточняю, почему мне нужно хранить такие функции. У меня в диспетчере событий есть карта, которая выглядит следующим образом:
std::map< const char*, std::vector<DelayedCaller> > _observers;
Я хочу иметь возможность вызывать функцию внутри "Задержанного вызывающего абонента" примерно так:
void Dispatcher::post( const EventBase& event ) const
{
// Side Note: I had to do this instead of map.find() and map.at() because
// passing a "const char*" was not evaluating as equal to event.type() even
// though event.type() is also a const char*. So instead I am checking it
// myself, which is fine because it gives me a little more control.
std::string type(event.type());
for( auto const &x : _observers ) {
std::string type2(x.first);
if ( type == type2 ) {
auto&& observers = x.second;
for( auto&& observer : observers ) {
// event may be any descendant of EventBase.
observer.slot->call(event);
}
break;
}
}
}
Мои слушатели сейчас выглядят так:
void AppBaseLogic::getValue(const EventBase &e) {
const EventDemo& demoEvent = static_cast<const EventDemo&>( e );
std::cout << demoEvent.type();
}
Я пытаюсь сохранить каждую функцию, чтобы аргумент мог выглядеть так:
void AppBaseLogic::getValue(const EventAnyDescendant &e) {
std::cout << e.type();
}
Надеюсь, это поможет. Спасибо всем, что нашли время, чтобы помочь мне с этим.
Дополнительное примечание по лямбдам: кто-то предложил их, я знаю, кто они и как их использовать, но я собираюсь сделать некоторые исследования, чтобы посмотреть, будет ли это иметь больше смысла. Я беспокоюсь за ремонтопригодность с ними, хотя из того, что я видел.
5 ответов
Хорошо, так что я знаю, что это сидит некоторое время. Я проводил тщательное исследование различных паттернов событий, пытаясь найти что-то ближе к тому, к чему я стремился. Прочитав все и по совету тех, кто оставил здесь комментарии, я решил использовать шаблон Сигнал / Слот, возможно, наиболее широко используемый шаблон событий для C++. Чтобы приблизиться к этому, нужно, чтобы все мои "логические классы" (будь то для графического интерфейса или для вычислений) сохраняли ссылку на третий "класс держателя события сигнала", который я называю посредником событий для простоты. Это почти так же хорошо, как я могу получить это. Любое событие, которое вам может понадобиться, может быть добавлено в этот класс, и к нему можно получить доступ и вызвать из любого класса со ссылкой на посредник событий. Я нашел довольно хороший класс сигналов, созданный Саймоном Шнигансом, но я активно пытаюсь найти / научиться делать что-то лучше (поточнее, может быть быстрее?). Если кто-то заинтересован / ищет помощь, как я, вы можете найти мой супер базовый тестовый пример здесь: https://github.com/Moonlight63/QtTestProject
Спасибо!
Не совсем понятно, какой у тебя DelayedCaller
делается. Если вы реорганизуете код и избавляетесь от него, вы получите только это:
auto c1 = []() {myFunc1(123, 45.6);}; // or use bind, the result is exactly the same
auto c2 = []() {myFunc2("A string");};
vector<function<void()>> v {c1, c2};
v[0]();
v[1](); // ok
Теперь, если вы попытаетесь ввести модификацию заполнителя в этой версии, станет понятно, почему она не сработала:
auto cSome = [](???) {getValueBasic(???)};
Что вы замените ???
с?
getValueBasic
принимает некоторый конкретный тип аргумента, и он просочится в cSome
подпись. Независимо от того, сколько шаблонных упаковщиков вы обернули, он будет просачиваться в подпись каждой обертки вплоть до самой внешней. bind
а также std::placeholders
не волшебная палочка, способная сделать ее несчастной.
Другими словами, если вы не знаете тип своей функции, вы не можете ее вызвать (вроде бы, не так ли?)
Один из способов стереть подпись и сделать так, чтобы все вызываемые объекты соответствовали одному и тому же типу, - это проверка типов и их приведение во время выполнения (иначе dynamic_cast
). Еще одна двойная отправка. Оба метода являются разными воплощениями одной и той же общей идеи посетителя. Google "шаблон посетителя" для получения дополнительной информации.
Взгляните на std:: bind и, возможно, std::mem_fn
Версия c+=11 способна выполнять всевозможные умные преобразования в вашем списке аргументов для генерации объекта, похожего на функцию.
Лямбды, конечно, обеспечивают еще большую гибкость, и вы можете смешивать их, в основном.
Я вижу две основные проблемы в вашей модифицированной (метод и заполнитель) версии DelayedCaller
(1) сейчас call()
получить параметр (типа T
) так func_()
вызывается с одним параметром; но func_()
оставаться определенным типа std::function<void()>
, поэтому не могу получить параметр [эта точка является причиной вашей ошибки "отсутствует функция"]
(2) если вы шаблонизируете call()
, получив параметр типа T
необходимо также шаблонизировать тип func_
что стало std::function<void(T)>
; так что вы должны шаблонизировать весь класс.
Принимая во внимание (1) и (2), и поддерживая std::unique_ptr
Переписал твой DelayedCaller
как dcM1
(M
для "метода" и 1
для "1 параметра")
template <typename T>
class dcM1
{
public:
template <typename TFunction, typename TClass>
static std::unique_ptr<dcM1> setup (TFunction && a_func,
TClass && a_class)
{
auto func = std::bind(std::forward<TFunction>(a_func),
std::forward<TClass>(a_class),
std::placeholders::_1);
return std::unique_ptr<dcM1>(new dcM1(func));
}
void call( T v ) const
{ func_(v); }
private:
using func_type = std::function<void(T)>;
dcM1(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
{ }
func_type func_;
};
и может быть использован следующим образом
auto cm1f = dcM1<int>::setup(&foo::func, &f);
auto cm1b = dcM1<long>::setup(&bar::func, &b);
cm1f->call(0);
cm1b->call(1L);
Ниже приводится полный рабочий пример
#include <iostream>
#include <string>
#include <functional>
#include <memory>
void myFunc1 (int arg1, float arg2)
{ std::cout << arg1 << ", " << arg2 << '\n'; }
void myFunc2 (char const * arg1)
{ std::cout << arg1 << '\n'; }
class dcVoid
{
public:
template <typename TFunction, typename... TArgs>
static std::unique_ptr<dcVoid> setup (TFunction && a_func,
TArgs && ... a_args)
{
return std::unique_ptr<dcVoid>(new dcVoid(
std::bind(std::forward<TFunction>(a_func),
std::forward<TArgs>(a_args)...)));
}
void call() const
{ func_(); }
private:
using func_type = std::function<void()>;
dcVoid(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
{ }
func_type func_;
};
template <typename T>
class dcM1
{
public:
template <typename TFunction, typename TClass>
static std::unique_ptr<dcM1> setup (TFunction && a_func,
TClass && a_class)
{
auto func = std::bind(std::forward<TFunction>(a_func),
std::forward<TClass>(a_class),
std::placeholders::_1);
return std::unique_ptr<dcM1>(new dcM1(func));
}
void call( T v ) const
{ func_(v); }
private:
using func_type = std::function<void(T)>;
dcM1(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
{ }
func_type func_;
};
struct foo
{ void func (int i) { std::cout << "foo func: " << i << std::endl; } };
struct bar
{ void func (long l) { std::cout << "bar func: " << l << std::endl; } };
int main ()
{
auto cv1 = dcVoid::setup(&myFunc1, 123, 45.6);
auto cv2 = dcVoid::setup(&myFunc2, "A string");
foo f;
bar b;
auto cm1f = dcM1<int>::setup(&foo::func, &f);
auto cm1b = dcM1<long>::setup(&bar::func, &b);
cv1->call();
cv2->call();
cm1f->call(0);
cm1b->call(1L);
}
Может быть, это вас устраивает. используя C++11
#include <iostream>
#include <functional>
#include <vector>
namespace test
{
std::vector<std::function<void()>> listeners;
template<typename F, typename... Args>
void add_listener(F call, Args&& ...args )
{
std::cout << "callback_dispatcher>" << __PRETTY_FUNCTION__ << "enter <<< " << std::endl;
auto invoke_me = [=]()mutable{
call(std::move(args)...);
};
listeners.push_back(invoke_me);
}
void dispatch_all()
{
for(auto func: listeners)
{
func();
}
}
}
int main()
{
std::cout << "Main entered..." << std::endl;
test::add_listener(
[](int a)
{
std::cout << "void(int) lambda dispatched with a = " << a << std::endl;
},
5
);
test::add_listener(
[](int a, std::string str)
{
std::cout << "void(int, string) lambda dispatched with a = " << a << ", str = " << str << std::endl;
},
10, "Hello World!"
);
test::dispatch_all();
std::cout << "Main exited..." << std::endl;
}
Выход:
Main entered...
callback_dispatcher>void test::add_listener(F, Args&& ...) [with F = main()::<lambda(int)>; Args = {int}]enter <<<
callback_dispatcher>void test::add_listener(F, Args&& ...) [with F = main()::<lambda(int, std::__cxx11::string)>; Args = {int, const char (&)[13]}]enter <<<
void(int) lambda dispatched with a = 5
void(int, string) lambda dispatched with a = 10, str = Hello World!
Main exited...
Обратитесь к SO_QUESTION, чтобы узнать, почему mutable и std::move используются при расширении аргументов в лямбде.