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

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

Изменено 29.12.2020 1:35 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 копированием все хорошо.
Ну и, в схеме с флагами этот кейс тоже не сложно обработать — вместо булевского флага сделать enum, в котором будут состояния по типу Ready, Deleted, New. В случае, когда мы в процессе вызова колбэка, добавлять не с Ready, а с New, а потом все New менять на Ready после завершения вызова колбэков.
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 после завершения вызова колбэков. Ну и, естественно, вместо for each придется использовать обычный for по индексам.
Или просто запоминать размер вектора перед вызовом первого колбэка. И ограничить цикл по колбэкам этим размером (все, что может добавиться в процессе, просто не будет вызвано).