Итак, теперь вы познакомились или вспомнили про задачу object pool и посмотрели на авторское решение. Вот оно у нас здесь открыто. Здесь мы видим реализацию шаблонного класса object pool. У него есть методы allocate для того, чтобы увидеть объект, deallocate, чтобы убрать — деструктор. Свободные объекты хранятся в очереди queue под названием free, занятые объекты в множестве allocate'ов. В функции allocate мы выделяем объекты, и в деструкторе ниже, вот здесь, мы объекты удаляем. Хорошо. Давайте теперь попробуем в этом решении найти ошибку. Для этого напишем некоторую тестирующую программку. Вот здесь. Заведём просто object pool и будем складывать в него объекты. Давайте пока пусть будут это просто объекты типа char. Не будем долго думать над тем, что там будет у нас находиться. Тут у нас pool. И дальше давайте теперь в цикле будем выделять объекты. 1000. Хорошо. Здесь напишем, что мы выделяем новый объект Allocate object, и собственно подаём функцию allocate. Pool allocate. Здесь вообще даже ничего не будем делать с тем объектом, который мы получили, просто мы его выделим. Такая будет простая программка. И в основной функции мы вызовем функцию run. Вот так вот это будет выглядеть. Давайте её соберём. Так, программа у нас успешно собралась, и давайте теперь её запустим. Запускаем. Вот мы видим, что программа у нас занимается тем, что просто выделяет некоторые объекты и пока ничего с ними не делает. Ну, хорошо. По крайней мере, мы видим, что она работает, цикл крутится. Уже не плохо. Наша же задача сейчас состоит в том, чтобы понять: объекты, которые у нас в пуле, на самом ли деле они у нас удалились в тот момент, когда удалился собственно пул. А сделаем мы это с использованием небольшого трюка. Мы заведём такой глобальный счётчик, в котором мы будем считать, сколько сейчас у нас объектов существует в программе, и заведём специальный класс под названием Counted. В своём конструкторе Counted будет заниматься тем, что он увеличивает этот счётчик. А в своём деструкторе он будет этот счётчик уменьшать. Тогда получается, что когда объект какой-то... вот этот объект создаётся, счётчик увеличивается. Когда объект уничтожается, счётчик уменьшается. И затем мы собственно взглянем на количество объектов по завершению работы программы. Вот теперь у нас, скажем, что в нашем пуле объектов хранятся именно объекты типа Counted, выведем значения счётчика до вот этого цикла, в котором мы выделяем объекты. Давайте напишем cout Counter before loop =. И вводим counter. Дальше, после того как у нас цикл отработал, снова напишем Counter after loop = counter и затем напишем, сколько у нас объектов осталось уже перед самым завершением работы программы. Мы понимаем, что в этот момент их должно быть уже ноль. Давайте напишем cout Counter и снова counter. Замечательно. Вот теперь эта программка у нас уже не такая бесполезная, как раньше. Она будет у нас считать собственно количество объектов. Мы её собираем. Хорошо. Давайте теперь запустим. Вот она снова выделяет объекты. Выделяет, выделяет... и когда она завершается, она выводит нам информацию о количестве объектов — то, что нам было нужно. Сразу после цикла этих объектов у нас 1000. Логично. У нас был цикл на тысячу итераций, мы выделили тысячу объектов. А перед завершением этих объектов осталось ноль. То есть они все удалились. Собственно, не забывайте, что, когда мы выходим из функции run, все локальные объекты этой функции уничтожаются. В том числе уничтожается наш пул объектов. А этот пул объектов в своем деструкторе, как мы помним, занимается тем, что уничтожает все объекты, которые он выделил. То есть это абсолютно корректное поведение программы. Ну и вроде бы пока всё работает неплохо, да? Хорошо. Однако давайте теперь создадим для программы специальные условия. То есть объекты мы же выделяем в памяти? А давайте сделаем так, что памяти у нас на самом деле не хватает. Это стандартная, в принципе, ситуация. Память — конечный ресурс. На сервере память вообще разделяется между многими приложениями, которые там запускаются, поэтому память в какой-то момент может кончиться. Чтобы нам было проще создать такую ситуацию, мы её сэмулируем. Давайте напишем вот такую магическую строчку, значение которой я объясню чуть позже: утилита appverif с ключиком verify, дальше мы укажем здесь имя исполнимого файла, faults, и укажем некоторые магические константы. Хорошо. И вот теперь запустим нашу программу. И мы видим, что наша программа начала падать, и она пишет, что случилось исключение под названием bad alloc. Давайте посмотрим, что же произошло. Мы с вами сейчас сэмулировали нехватку памяти. То есть в системе-то память была, но вот нашей программе она эту память не отдала. Мы это сделали с помощью утилиты appverifier. Запускается она с помощью исполнимого файла appverif. Она входит в стандартную поставку Windows SDK. И у нее есть следующие параметры: ну во-первых, мы сначала указываем /verify и имя исполнимого файла, для которого мы применяем подобное поведение, дальше /faults и указываем два числа. Первое — это вероятность того, что попытка выделения динамической памяти на самом деле вернёт нам ошибку со стороны операционной системы. Она указывается как целое число из миллиона. Здесь мы указали — такой вот интересный интерфейс у этой утилиты — здесь мы указали 10 000 из миллиона, то есть 1 из 100, то есть 1 %. Короче, с вероятностью 1 % у нас попытка увеличить память вернёт ошибку. И дальше указали число 200 — это количество миллисекунд, в течение которых наша программа будет работать нормально. Но чтобы её нормально протестировать, она вообще-то должна сначала загрузиться, она должна загрузить runtime, запуститься функция run, и уже только после этого нам нужно делать так, чтобы у нас возникали какие-то проблемы в выделении. Это мы делали под Windows. Под Linux вы можете сделать очень похожую ситуацию, воссоздать с помощью утилиты ulimit. Ей вы указываете... Она работает немножко по-другому. Там вы указываете не вероятность, вы просто указываете максимальный объём виртуальной памяти, который будет доступен для всех процессов, которые сейчас выполняются в рамках текущей сессии. Обратите внимание, что, чтобы воспроизвести подобное поведение на вашей машине, вам, может быть, придётся немножко изменить константы, может быть, увеличить количество итераций в цикле выделения объектов. Потому что нехватка памяти, это такая тонкая материя... такие баги нужно еще уметь отловить. Здесь, в общем-то, мы подготовились и выбрали правильные константы. Итак, сейчас нам среда написала, что у нас выбрасывается исключение bad alloc. Ну, хорошо. Давайте попробуем его отловить и как-то на него отреагировать. Вот этот наш цикл, где у нас происходит выделение. Засунем его в блок try. Дальше здесь напишем catch. Bad alloc cout ewhat endl. Окей. То есть теперь мы исключения отлавливаем. Давайте соберём программу. Хорошо. Давайте теперь её запустим. Вот мы видим, что у нас программа перестала падать. Теперь вместо падения она выводит сообщения из текста исключения. В данном случае это просто bad alloc. Она говорит, что она успела выделить 49 объектов, и, самое интересное, это количество объектов, которое у нас находится перед завершением программы, количество объектов, которые сейчас живут в программе. И мы видим, что внезапно это количество стало равно 1. То есть какой-то объект у нас не удалился. Этот объект у нас утёк. Давайте запустим ещё несколько раз программу. Здесь у нас Counter before exit 1. Ещё раз запустим. Снова 1. А вот теперь смотрите: мы запустили ещё несколько раз — ещё интереснее! В этот раз у нас все объекты удалились. То есть теперь никто не утёк. Получается достаточно странное поведение программы, плохо предсказуемое. Объект то утекает, то не утекает, и подобное поведение у нас возникло из-за того, что мы ограничили память, доступную программе. Это не очень хорошо. Программа, вообще говоря, должна быть готова к тому, что ей не хватает памяти, и корректно реагировать на данные ситуации. А что же с этим делать — мы с вами поговорим в следующем видео.