Информация об изменениях

Сообщение Re[18]: Исповедь C++ника от 29.12.2020 0:25

Изменено 29.12.2020 0:40 Lexey

Re[18]: Исповедь C++ника
Здравствуйте, so5team, Вы писали:

S>С простым копированием не так все просто: RemoveOnButtonPress может вызываться не только для удаления текущего обработчика, но и для любого другого. В том числе и для тех обработчиков, до которых внутри for_each-а пока не дошли. Если for_each будет идти по временному контейнеру, то получится, что во временном контейнере останется обработчик, который уже изъят из основного. И будет вызван. Что может сильно удивить пользователя, который этот обработчик только что изъял.


Это уже совсем странный use case. Но, теоретически возможный, да.

S>Тут нужна более хитрая схема. Например, с дополнительным флагом valid/invalid для каждой подписки. Если подписка удаляется в момент публикации (т.е. внутри for_each-а), то физически она не удаляется, а флаг для нее меняется на invalid (+ еще взводится отдельный флаг, который показывает, что список подписок изменился). Перед вызовом подписчика проверяется флаг для него. Если valid, то вызывается. Если invalid, то пропускается. После выхода из for_each-а публикации проверяется общий флаг наличия изменений в списке. Если изменения есть, то идет еще один цикл с изъятием всех invalid подписок.


А можно просто сказать, что из обработчика события безопасно отписывать можно только его самого. Иначе можно получить гонку.
Или перед вызовом колбэков копировать их не в вектор, а в очередь, и при отписке вычищать колбэк и из исходного вектора и из очереди.

S>А если добавить сюда еще и возможность вызова AddOnButtonPress в момент публикации, то ситуация становится еще интереснее. И, возможно, использование std::list вместо std::vector в таких условиях более оправдано, не смотря на все недостатки std::list-а.


Тут, как раз, в схеме c копированием все хорошо.
Re[18]: Исповедь C++ника
Здравствуйте, so5team, Вы писали:

S>С простым копированием не так все просто: RemoveOnButtonPress может вызываться не только для удаления текущего обработчика, но и для любого другого. В том числе и для тех обработчиков, до которых внутри for_each-а пока не дошли. Если for_each будет идти по временному контейнеру, то получится, что во временном контейнере останется обработчик, который уже изъят из основного. И будет вызван. Что может сильно удивить пользователя, который этот обработчик только что изъял.


Это уже совсем странный use case. Но, теоретически возможный, да.

S>Тут нужна более хитрая схема. Например, с дополнительным флагом valid/invalid для каждой подписки. Если подписка удаляется в момент публикации (т.е. внутри for_each-а), то физически она не удаляется, а флаг для нее меняется на invalid (+ еще взводится отдельный флаг, который показывает, что список подписок изменился). Перед вызовом подписчика проверяется флаг для него. Если valid, то вызывается. Если invalid, то пропускается. После выхода из for_each-а публикации проверяется общий флаг наличия изменений в списке. Если изменения есть, то идет еще один цикл с изъятием всех invalid подписок.


А можно просто сказать, что из обработчика события безопасно отписывать можно только его самого. Иначе можно получить гонку.
Или перед вызовом колбэков копировать их не в вектор, а в очередь, и при отписке вычищать колбэк и из исходного вектора и из очереди. Впрочем, это уже будет не сильно проще, чем вариант с флагами.

S>А если добавить сюда еще и возможность вызова AddOnButtonPress в момент публикации, то ситуация становится еще интереснее. И, возможно, использование std::list вместо std::vector в таких условиях более оправдано, не смотря на все недостатки std::list-а.


Тут, как раз, в схеме c копированием все хорошо.
Ну и, в схеме с флагами этот кейс тоже не сложно обработать — вместо булевского флага сделать enum, в котором будут состояния по типу Ready, Deleted, New. В случае, когда мы в процессе вызова колбэка, добавлять не с Ready, а с New, а потом все New менять на Ready после завершения вызова колбэков.