[БЕЗ_ЗВУКА] В предыдущем видео мы с вами посмотрели, как неопределенное приведение в программе может привести к очень неожиданным результатам ее работы. Однако код, который мы рассмотрели, вряд ли можно считать хорошим примером современного C++. Поэтому сейчас подумайте, как можно было бы решить проблему с неопределенным поведением, которая у нас была в той программе, с использованием более правильного идиоматического C++. Как вы, наверное, могли догадаться, основная проблема в предыдущей программе заключается в том, что у нас размер массива хранился отдельно от самого массива. То есть у нас сущность одна — массив, а переменных две — сам массив и его размер. Но это на самом деле характерно для C, но не для C++, где мы для массива и контейнера можем непосредственно спросить его размер. Соответственно если бы в той программе мы использовали идиоматически правильный C++, то проблема бы у нас, в принципе, не возникла. И действительно, во многих случаях использование наиболее правильного современного C++ позволяет избегать неопределенного поведения, но, конечно, не всегда. Давайте сейчас рассмотрим пример, когда неопределенное поведение возникнет, даже несмотря на то, что мы вроде бы все делаем правильно. Давайте напишем небольшую программу, которую мы будем использовать для нашей системы домашней сигнализации. Предположим, что наша система может по-разному реагировать на внешний раздражитель, и ее реакция определяется некоторым указателем на функцию. Заведем тип указатель на функцию. Вот так в C++ записывается тип указатель на функцию, который ничего не принимает и возвращает void. И соответственно определим некоторый текущий обработчик, то есть функция, которую мы будем вызывать, если у нас в данный момент возникает какой-то раздражитель. Объявим ее для простоты просто глобальной переменной и инициализируем нулевым указателем. Хорошо. Теперь давайте напишем один из возможных обработчиков. Скажем, некоторый агрессивный обработчик. Назовем его HostileAlarm. И он будет заниматься тем, что будет спускать на атакующего велосираптора. Окей. Пока это у нас будет единственный обработчик, который мы используем. Дальше мы, пожалуй, не хотим, чтобы кто угодно из любой части нашей программы мог выставлять этот обработчик. Вместо этого мы заведем некоторую функцию, которую нужно будет вызывать и которая сама выставит обработчик и, возможно, произведет некоторое дополнительное логирование. Давайте заведем такую функцию SetHostileAlarm, которая присвоит нашему обработчику HostileAlarm и сделает дополнительное логирование: Hostile alarm set. Хорошо. И теперь, поскольку мы собираемся для выставления обработчика использовать только такого рода функции, сделаем так, чтобы из других мест к обработчику обратиться было нельзя. Для этого мы поместим его в так называемое анонимное пространство имен. Анонимное пространство имен — это важный частный случай пространства имен. Сущности, которые в нем лежат, для того чтобы к ним обратиться из внешнего пространства имен, не нужно никакой дополнительной квалификации, как бы мы писали в случае с обычным пространством имен — его имя и четвероточие. Но при этом к этим сущностям можно обращаться только из текущего модуля трансляции. В других модулях трансляции они просто не доступны. И это ровно то, что нам нужно, ведь мы собираемся использовать специальные функции, для того чтобы обращаться к этому обработчику. И давайте теперь заведем некоторую функцию Test, которая будет заниматься тем, что вызывать текущий обработчик и выводить сообщение о том, что тест прошел успешно. Test succeeded. Здесь, поскольку у нас обработчик может быть нулевым, нам, конечно, следует проверить, не является ли обработчик в данный момент нулевым. Но, допустим, мы забыли это сделать. И у нашей функции мы только вызовем тест. Вот так. Мы написали такую программу и сейчас хотим залить ее на прошивку нашей сигнализации, просто для того чтобы убедиться, что она компилируется, запускается и выводит сообщение о том, что тест пройден успешно. Мы не вызываем функции для того, чтобы установить какой-то обработчик. Поэтому мы не ожидаем, что что-то вообще произойдет. Мы просто хотим увидеть, что тест выполнен успешно. Давайте запустим эту программу. [БЕЗ_ЗВУКА] Так, здесь лишние скобки. Окей. Смотрите, тест у нас, конечно, прошел успешно. Но вместе с тем оказалось, что мы выпустили велосираптора. И он нас атаковал. И в принципе, не самое хорошее завершение вечера. Помните, я вас предупреждал, что такое может произойти? Нужно быть аккуратнее с неопределенным поведением. Но вообще это достаточно неожиданное поведение, потому что мы же нигде не устанавливали тот обработчик, который выпускает велосираптора. Кроме того, вот у нас есть функция, которая его устанавливает, она выводит некоторое логирование. Этого логирования мы в выводе не обнаружили. Что же произошло? Давайте снова притворимся компилятором и попробуем понять, как он рассуждал. Вот он видит функцию Test. Функцией Test вызывается текущий обработчик. Поскольку этот обработчик не проверяется на ноль, то компилятор предполагает, что этот обработчик не является нулевым. Потому что если бы он был нулевым, то это было бы неопределенное поведение. Но ведь программист же обещал, что неопределенного поведения не произойдет. Правильно? Правильно. Ну, компилятор нам верит и поэтому считает, что здесь указатель не нулевой. Тогда он пытается понять, хорошо, а где вообще в программе этому указателю присваивается какое-либо ненулевое значение? А такое место у нас только одно — вот оно. Здесь обработчику присваивается функция HostileAlarm. И компилятор думает: ладно, если здесь обработчик ненулевой и единственное ненулевое значение, которое ему присваивается, оно находится вот здесь, ему присваивается HostileAlarm. Значит, и там он равен HostileAlarm и собственно его вызывает. А то, что это присваивание происходит в функции, которая на самом деле ни разу не вызывается, извините, так уж получилось. Не нужно обманывать компилятор. Смотрите, что получилось. Что у нас здесь в этой программе мы на самом деле используем только идиоматический C++. Но несмотря на это, у нас все равно возникла ситуация, в которой неопределенное поведение. В следующем видео мы посмотрим на еще один похожий пример.