Многие функции принимают указатель на функцию в качестве аргумента. atexit
а также call_once
отличные примеры. Если эти функции более высокого уровня принимают пустой аргумент *, такой как atexit(&myFunction, &argumentForMyFunction)
Тогда я мог бы легко обернуть любой понравившийся функтор, передав указатель функции и блок данных для обеспечения сохранности состояния.
Как и во многих случаях, я хотел бы зарегистрировать обратный вызов с аргументами, но функция регистрации не позволяет мне передавать какие-либо аргументы. atexit
принимает только один аргумент: функция принимает 0 аргументов. Я не могу зарегистрировать функцию для очистки после моего объекта, я должен зарегистрировать функцию, которая очищает после всех объектов класса, и заставлять мой класс вести список всех объектов, нуждающихся в очистке.
Я всегда рассматривал это как упущение, и, казалось, не было веской причины, по которой вы бы не позволили передавать жалкий 4 или 8-байтовый указатель, если только вы не используете чрезвычайно ограниченный микроконтроллер. Я всегда предполагал, что они просто не понимали, насколько важен этот дополнительный аргумент, пока не стало слишком поздно переопределять спецификацию. В случае call_once
версия posix не принимает аргументов, но версия C ++ 11 принимает функтор (который практически эквивалентен передаче функции и аргумента, только компилятор выполняет за вас часть работы).
Есть ли какая-то причина, по которой можно было бы не допустить этот дополнительный аргумент? Есть ли преимущество в том, чтобы принимать только «пустые функции с 0 аргументами»?
Я думаю atexit
это просто особый случай, потому что любая функция, которую вы передаете ей, должна вызываться только один раз. Поэтому любое состояние, в котором он нуждается, может быть сохранено в глобальных переменных. Если atexit
были разработаны сегодня, вероятно, потребуется void*
чтобы вы могли избежать использования глобальных переменных, но это не дало бы ему никакой новой функциональности; в некоторых случаях это просто сделало бы код немного чище.
Однако для многих API-функций обратным вызовам разрешено принимать дополнительные аргументы, и недопущение их использования будет серьезным недостатком проекта. Например, pthread_create
позволяет вам пройти void*
Это имеет смысл, потому что в противном случае вам потребуется отдельная функция для каждого потока, и было бы совершенно невозможно написать программу, которая порождает переменное число потоков.
Довольно много интерфейсов, принимающих указатели на функции, не имеющие аргумента передачи, просто происходят из другого времени. Однако их подписи не могут быть изменены без нарушения существующего кода. Это своего рода неправильный дизайн, но это легко сказать задним числом. Общий стиль программирования перешел к ограниченному использованию функционального программирования в основном в нефункциональных языках программирования. Кроме того, в то время как многие из этих интерфейсов были созданы для хранения каких-либо дополнительных данных даже на «обычных» компьютерах, это требовало заметных дополнительных затрат: помимо используемого дополнительного хранилища, дополнительный аргумент также необходимо передавать, даже если он не используется. Конечно, atexit()
вряд ли это узкое место в производительности, поскольку он вызывается только один раз, но если вы будете передавать лишний указатель везде, у вас наверняка также будет один qsort()
функция сравнения.
Специально для чего-то вроде atexit()
довольно просто использовать пользовательский глобальный объект, с помощью которого регистрируются объекты функций, вызываемые при выходе: просто зарегистрируйте функцию с atexit()
вызов всех функций, зарегистрированных в указанном глобальном объекте. Также обратите внимание, что atexit()
гарантируется регистрация только до 32 функций, хотя реализации могут поддерживать больше зарегистрированных функций. Кажется нецелесообразным использовать его как реестр для функции очистки объекта, а не как функцию, вызывающую функцию очистки объекта, так как другие библиотеки могут также нуждаться в регистрации функций.
Тем не менее, я не могу себе представить, почему atexit()
Это особенно полезно в C ++, где объекты автоматически уничтожаются после завершения программы в любом случае. Конечно, этот подход предполагает, что все объекты каким-то образом удерживаются, но это обычно необходимо в любом случае в той или иной форме и обычно выполняется с использованием соответствующих RAII объекты.