#c #user-interface #pointers #sfml
#c #пользовательский интерфейс #указатели #sfml
Вопрос:
В настоящее время я создаю свою собственную библиотеку GUI на основе SFML. На данный момент я работаю над кнопкой. Поэтому при создании кнопки вам также необходимо указать обратный вызов, который представляет собой функцию, выполняемую при нажатии кнопки.
Теперь я отвечаю мне, каковы недостатки использования только указателя на функцию в качестве обратного вызова кнопки, потому что я не знаю ни одной популярной библиотеки GUI, которая делала бы это так просто. Если функция обратного вызова является длительным процессом, я бы выполнил ее в новом потоке, но в данный момент я не уверен в этом.
Итак, каковы были бы причины не использовать такое простое решение и особенно, что было бы лучшим способом?
Комментарии:
1. Как вы указали, если функция выполняет какую-либо нетривиальную работу, это приведет к зависанию пользовательского интерфейса, что пользователи ненавидят . Вот почему большинство библиотек GUI имеют выделенный поток для пользовательского интерфейса, и вам не разрешается делать что-либо «тяжелое» в этом потоке. Я думаю, что большинство современных библиотек GUI используют какую-то систему очереди событий (что может в конечном итоге упростить реализацию других функций, таких как отмена / повтор).
2. Я бы предложил указать обратные вызовы с помощью лямбда-замыканий. Таким образом, благодаря захвату, замыкание может взаимодействовать с чем угодно в исходной среде без необходимости пересылать
void *
или полагаться на OO-материалы, такие как наследование, которые создают много связей между различными частями приложения. И, конечно, если вы будете очень осторожны, закрытие может запускать (или взаимодействовать с) потоками…3. @0x5453 Да, я бы решил это, выполнив обратный вызов в другом потоке, но это все равно будет указатель на функцию
4. Все библиотеки GUI, о которых я знаю, выполняют обратный вызов в потоке GUI, большинство из них синхронно, но некоторые из них из очереди событий. Если вы хотите выполнять тяжелую работу в обратном вызове, вы должны написать обратный вызов, который делегирует работу другому потоку и быстро возвращается.
Ответ №1:
Это сложная проблема!
Указатели на функции просты в реализации на стороне отправителя, но их сложно использовать на стороне получателя, потому что у них нет никакого контекста.
Одна из проблем заключается в том, что указатель на функцию не может указывать на функцию-член. Вот почему вы часто видите, как фреймворки (в стиле C) передают произвольное void *userData
значение своим обратным вызовам, чтобы вы могли привести свой this
указатель и получить его таким образом. Для этого вам все еще нужно написать статическую функцию-оболочку для приведения указателя обратно и вызова функции-члена.
Более современным решением было бы использовать std::function
. Это может содержать обычный указатель на функцию, указатель на функцию-член, а также лямбда или функтор.
Однако, когда вы добавляете контекст подобным образом (или каким-либо другим способом), вы быстро сталкиваетесь с трудностями со временем жизни. Что должно произойти, когда принимающий класс уничтожается перед отправителем? Если вы ничего не сделаете, эта ситуация приведет к неопределенному поведению. Решение состоит в том, чтобы отслеживать на стороне получателя, на какие события подписан получатель, и отменить их привязку до уничтожения получателя. И это нужно делать в обоих направлениях: когда отправитель уничтожается, ему также необходимо уведомить получателя о том, что он должен забыть об отправителе, иначе получатель позже попытается отменить привязку события, которое больше не существует.
И я еще даже не начал думать о многопоточности…
Например, существуют библиотеки, которые решают эти проблемы различными способами eventpp
(только что найденные с помощью веб-поиска, это не одобрение).
Еще следует упомянуть Qt toolkit, который зашел так далеко, что написал свои собственные небольшие сигналы и слоты расширения для языка C (реализованные в виде генератора кода и кучи макросов), чтобы решить эту проблему очень эргономичным способом.
Ответ №2:
каковы недостатки использования только указателя на функцию в качестве обратного вызова кнопки
Передача некоторого контекстного аргумента этой функции была бы удобной. Я имею в виду, что в пользовательском интерфейсе может быть много кнопок, выполняющих одно и то же действие с различными объектами. Подумайте, может быть, о кнопке «отправить сообщение» рядом с каждым ником в списке друзей.
Таким образом, вы можете захотеть, чтобы ваша кнопка передавала некоторые контекстные аргументы вызову. Но поскольку мы говорим о C , это лучше абстрагировать как
struct IButtonAction
{
virtual void OnAttached() = 0;
virtual void OnDetached() = 0;
virtual void OnClick() = 0;
};
И пусть клиентский код реализует этот интерфейс, сохраняя в зависимости Arg1
от того, Arg2
, и т.д. в каждом объекте экземпляра.
Класс button будет вызывать OnAttached
/ OnDetached
когда он начинается / заканчивается, используя указатель на экземпляр этого интерфейса обратного вызова. Эти вызовы должны быть сопряжены. Клиентская реализация этих методов может выполнять управление временем жизни и синхронизацию с OnClick
, если требуется.
OnClick
метод выполняет действие.
Я не думаю, что кнопка должна беспокоиться о потоках. Клиентский код несет ответственность за решение о том, следует ли создавать поток для длительного действия.