[БЕЗ СЛОВ] Обсудим такую сущность как деструкторы.
Что такое деструктор?
Деструктор вызывается при уничтожении объекта.
Вы уже знаете, что есть специальные методы,
которые вызываются при создании объекта, — называются конструкторы.
Логично было бы предположить, что при уничтожении объектов также вызываются
некоторые специальные методы, они называются деструкторами.
Что принято делать в деструкторах?
В деструкторах, как правило, откатывают, отменяют те действия,
которые были сделаны в конструкторе или в других методах.
Например, если вы почему-то вручную выделяли память в конструкторе
или других методах, вы должны ее освободить в деструкторе.
Если вы открыли файл в конструкторе или в каком-то методе,
вы в деструкторе должны его закрыть.
Или, на самом деле, в деструкторе можно делать всё, что вам нравится, всё,
что вам нужно.
Например, вывести какую-то отладочную информацию.
Давайте этим и займемся.
Рассмотрим снова наш класс маршрута.
В нём уже есть конструкторы: конструктор без параметров, конструктор с параметрами,
и методы GetSource, GetDestination, GetLength и так далее.
Давайте вот что сделаем.
Мы в нашем классе вызываем метод UpdateLength периодически,
и как будто бы в реальной жизни он может быть долгим —
это сейчас у меня написано как заглушка для функции ComputeDistance
какое-то странное вычисление расстояния между городами — на самом деле нет,
в реальности это может быть вхождение в какую-то базу данных.
Поэтому этот метод может быть долгим и поэтому в какой-то момент может
получиться так, что наш класс работает долго.
Мы хотим понять почему, подозреваем в замедлении работы метод UpdateLength, даже
функцию ComputeDistance, и хотим как-то замерить для конкретного объекта маршрута,
для каких городов мы вызывали на протяжении всей жизни этого объекта метод UpdateLength.
Как мы это будем делать?
Давайте создадим так называемый лог вызовов функции ComputeDistance.
То есть мы сделаем вектор в нашем классе — вектор строк —
который мы назовём compute_distance_log.
В него будем писать информацию о том,
для каких городов вызывалась функция ComputeDistance.
Раз мы используем вектор, мы должны добавить
заголовочный файл vector, и теперь нужно этот вектор
заполнять в методе UpdateLength.
Пишем: compute_distance_log.push_back от чего?
От тех городов, для которых мы вызываем функцию ComputeDistance.
То есть source — давайте разделим дефисом — и destination.
Отлично.
Давайте проверим, что класс компилируется.
Кажется, всё хорошо.
Теперь давайте найдем место, где можно вывести этот лог.
Можно было бы сделать какой-то метод, который этот лог просто выведет,
а можно сказать компилятору: как только объект уничтожится, выведи, пожалуйста,
весь этот лог вызова функции ComputeDistance.
Для этого мы напишем деструктор.
Как мы напишем деструктор?
У него нет возвращаемого значения, как и у конструктора,
потому что он просто уничтожает объекты и всё,
и у него название выглядит вот так: тильда и название класса,
в данном случае — ˜ Route.
В этом деструкторе мы можем написать какой угодно код.
Например, мы можем пройтись циклом по compute_distance_log.
Давайте переберем с переменной entry по compute_distance_log,
обычный цикл по этому
самому вектору, и здесь мы просто в cout выводим эту строчку лога.
В конце пишем перевод строки,
чтобы каждая строчка лога была на отдельной строке в файле, в выводе нашем.
Казалось бы, можно компилировать код.
Отлично, код компилируется.
Давайте теперь, чтобы посмотреть, как он работает, создадим переменную маршрута.
Давайте изначально у нас будет маршрут от Москвы до Санкт-Петербурга,
потом мы вызовем метод SetSource,
[БЕЗ СЛОВ] поменяем
начало маршрута, например, на Выборг,
а конец маршрута поменяем на Вологду.
Что же выведет этот код?
Компилируем, всё хорошо,
запускаем и видим тот самый compute_distance_log.
Изначально мы создали маршрут от Москвы до Санкт-Петербурга.
Мы видим,
что вызвалась функция ComputeDistance от Москвы и Санкт-Петербурга.
Затем мы поменяли начало маршрута на Выборг, маршрут стал от Выборга до
Санкт-Петербурга, что мы и видим в нашем отладочном выводе.
Наконец, мы поменяли конец маршрута на Вологду,
и вызвалась функция ComputeDistance от Выборга и Вологды.
Давайте посмотрим в отладчике,
как же у нас происходили вызовы конструкторов и деструктора.
Запускаем.
Итак, вот мы находимся в первой строчке,
хотим создать объект маршрута.
Понятно, что мы сразу попадаем в конструктор —
конструктор от двух параметров.
В этом конструкторе мы инициализируем поле source, инициализируем поле destination
и вызываем метод UpdateLength.
Можем посмотреть, как он вызывается.
Сначала мы вызываем функцию ComputeDistance, попадаем в неё,
затем эта функция завершилась,
и здесь мы добавляем новую запись в compute_distance_log, как и ожидалось.
Отлично, всё, конструктор закончился,
переходим на следующую строчку функции main, вызываем метод SetSource от Выборга.
В этом методе мы меняем значение поля source и опять вызываем метод
UpdateLength, в котором, как и ожидалось, вызываем функцию ComputeDistance,
затем добавляем новую запись в compute_distance_log.
Отсюда мы тоже выходим.
И, наконец, вызываем метод SetDestination, в котором происходит ровно то же самое.
Мы меняем destination, попадаем в метод UpdateLength,
здесь вызываем функцию ComputeDistance
и затем добавляем новую запись в compute_distance_log.
Отлично, теперь нам пора выходить из функции main,
объект должен перестать существовать, он больше не сможет быть использован,
поскольку мы выйдем из функции main, объект route нужно уничтожить.
Когда объект нужно уничтожить, компилятор вызывает деструктор,
вот тот самый метод ˜ Route вызывается, и здесь тот самый цикл for.
В этом цикле for мы выводим по одному элементу нашего вектора.
Вот и всё, мы их все вывели, объект уничтожился.