Недостатки использования указателя на функцию в качестве обратного вызова кнопки пользовательского интерфейса

#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 метод выполняет действие.

Я не думаю, что кнопка должна беспокоиться о потоках. Клиентский код несет ответственность за решение о том, следует ли создавать поток для длительного действия.