Обсудим такую сущность как константность методов.
Давайте посмотрим на наш класс «вживую».
Итак, что у нас есть?
У нас есть та самая функция ComputeDistance, которая принимает на вход
названия городов, где маршрут начинается, где маршрут заканчивается.
Она как-то реализована, вот я совсем для примера решил вычесть из
длины названия source длину названия destination.
Затем у нас есть класс Route, в котором есть метод GetSource,
метод GetDestination, метод GetLength, метод SetSource,
SetDestination и приватный метод UpdateLength, и соответственно три поля.
Давайте попробуем написать какую-нибудь функцию, которая будет что-то делать
с нашим классом, например, функцию, которая распечатает информацию о маршруте.
Соответственно, функция будет возвращать void, называться PrintRoute,
и она должна принять на вход маршрут.
Логично принять его по константной ссылке, чтобы лишний раз не копировать.
Соответственно, пишем: (const Route& route).
Функция принимает константную ссылку на маршрут.
Теперь я должен вывести информацию о маршруте.
Как я это делаю?
Я пишу просто cout и поочередно обращаюсь к методу GetSource
и к методу GetDestination.
Ну и, скажем, выведу в конце перевод строки.
Наверное, еще стоит разделить город, где маршрут начинается, и город, где маршрут
заканчивается, каким-нибудь разделителем, например, поставим тут дефис.
Хорошо.
Ну и для читаемости сделаем перевод строки.
Итак, будет ли у нас работать такой код?
Попробуем его скомпилировать.
Код компилируется.
И как будто бы все хорошо.
Давайте попробуем эту функцию вызвать, создадим какой-нибудь маршрут.
[ЗВУК]
Запускаем код.
[ЗВУК] Так,
и код не запустился.
Что происходит?
Да, я опечатался в названии функции.
Давайте поправим.
Еще раз скомпилируем.
Да, отлично.
Всё. Теперь мы попробовали
вызвать функцию PrintRoute для маршрута, и у нас проблема.
В чем же проблема?
Проблема в том, что метод GetSource — вот давайте на него
посмотрим — в нем нигде не написано, что он не имеет права менять текущий объект.
Мы в этом методе можем просто взять и поменять, например, поле source,
ничто этому не мешает — с одной стороны.
С другой стороны, в функции PrintRoute на вход подается константная ссылка
на маршрут, то есть эта функция не имеет права поменять переданный ей маршрут.
А вот тот метод GetSource, который вызывается от этой константной ссылки,
он может поменять этот объект.
Поэтому компилятор недоволен, компилятор не дает нам вызвать этот самый метод из
такой функции по константной ссылке на маршрут.
Что нужно сделать?
Нужно указать в методе GetSource, что он не имеет права
менять переданный объект — объект, в контексте которого он вызван.
Как мы это делаем?
Мы объявляем метод константным.
Для этого мы здесь пишем слово const.
И, соответственно, метод GetDestination тоже должен быть константным,
и метод GetLength тоже должен быть константным.
Попробуем теперь скомпилировать наш код.
Кажется, все хорошо.
Запускаем.
Но у нас изначально маршрут ничем не заполнен,
давайте его чем-нибудь заполним, чтобы было поинтереснее.
Допустим, будет маршрут от Москвы
до — SetDestination — до Вологды.
Запускаем код и видим, что у нас маршрут от Москвы до Вологды.
Код скомпилировался, нам здесь помогла константность методов.
Итак, на самом деле изначально, когда мы писали наш класс, если бы мы знали,
что такое константные методы, мы должны были сразу объявить методы GetSource,
GetDestination, GetLength константными, потому что они текущий объект не меняют.
А давайте попробуем, например, объявить константным метод SetSource.
Что у нас произойдет?
Компилируем код.
Не получилось.
Почему не получилось?
Давайте посмотрим сообщение об ошибке.
Вот, например, самое первое — компилятор ругается на строчку source = new_source.
Почему на нее ругается?
Потому что метод якобы константный, поэтому объект менять нельзя,
а именно в этой строчке мы его меняем.
На самом деле есть еще одно сообщение об ошибке, если прокрутить чуть ниже.
Следующее — я не могу вызвать метод UpdateLength.
Почему я не могу его вызвать?
Давайте посмотрим на сообщение об ошибке.
На самом деле, как правило, сообщения об ошибках,
даже если они на английском языке, они понятные.
Но вот в случае с константностью важно обратить внимание,
что компилятор всегда пишет примерно одну и ту же фразу: «передача „const Route“
куда-то discards qualifiers».
Как раз вот эта магическая фраза «discards qualifiers» означает,
что у вас проблема с константностью.
В данном случае вы пытаетесь вызвать из якобы константного метода SetSource
неконстантный метод UpdateLength.
Итак, здесь константность не нужна, и даже если вы ее здесь напишете,
компилятор выведет сообщение об ошибке.
Давайте попробуем еще сделать какую-нибудь странную вещь, например,
из функции PrintRoute попробовать поменять объект.
Напишем route.SetSource от...
Прямо в функции печати маршрута зачем-то попробуем изменить город,
где маршрут начинается.
Посмотрим на сообщение об ошибке.
Вот, компилятор пишет, что он не может вызвать функцию SetSource.
Почему?
Потому что «передача „const Route“ discards qualifiers» — опять же проблема
с константностью.
Мы видим, что не надо по константной ссылке вызывать метод SetSource,
потому что он меняет текущий объект.
Отлично.
Это нам не нужно.
Давайте напишем еще какой-нибудь метод, который будет принимать маршрут уже
по неконстантной ссылке, например, метод ReverseRoute.
Он принимает маршрут по обычной ссылке, потому что мы хотим маршрут перевернуть.
Давайте запомним в строковые переменные старое
начало маршрута и старый конец маршрута.
Вызываем метод GetSource.
Вызываем метод...
Для переменной old_destination вызываем метод
GetDestination.
И теперь вызываем методы SetSource, SetDestination,
передавая в них old_destination и old_source.
route.SetSource(old_destination) — то,
что раньше было концом маршрута, становится его началом.
А новым концом маршрута становится то,
что раньше было началом.
И давайте попробуем этот самый маршрут перевернуть.
ReverseRoute(route).
Скомпилируется ли этот код?
Давайте посмотрим.
Да, скомпилируется, давайте попробуем его запустить.
Маршрут от Вологды до Москвы.
Никаких проблем нет.
Что хотелось показать?
Что по неконстантной ссылке на маршрут мы можем вызвать
как константные методы GetSource или GetDestination,
так и неконстантные методы SetSource и SetDestination.
Итак, подведем итоги.
В классах бывают поля и методы.
Методы могут быть константными и неконстантными.
Константные методы не имеют права менять текущий объект.
Если вы пишете метод, который текущий объект не меняет, например,
отдает начало или конец маршрута или его длину, не меняет сам маршрут,
то метод надо объявить константным, написав ключевое слово const.
Если же метод меняет текущий объект, то он должен быть неконстантным.
При этом по константной ссылке вы можете вызвать только константный метод,
а по неконстантной ссылке — какой угодно метод текущего объекта.