Урок №123. Классы и const

  Юрий  | 

    | 

  Обновл. 16 Июн 2019  | 

 9013

 ǀ   2 

В уроке №37 мы узнали, что фундаментальные типы данных (int, double, char и т.д.) можно сделать константными, используя ключевое слово const, и что все константные переменные должны быть инициализированы во время объявления. В случае с константными фундаментальными типами данных инициализация может быть копирующей, прямой или uniform:

Константные объекты классов

Объекты классов можно сделать константными (используя ключевое слово const). Инициализация выполняется через конструкторы классов:

Как только константный объект класса инициализируется через конструктор, то любая попытка изменить переменные-члены объекта запрещена, так как это нарушает принципы константности объекта. Запрещается как изменение переменных-членов напрямую (если они являются public), так и вызов методов (сеттеров), с помощью которых можно установить значения переменным-членам. Рассмотрим следующий класс:

Строчки №16-17 вызовут ошибки компиляции, так как они нарушают принципы константности объекта, пытаясь напрямую изменить переменную-член, и вызывая сеттер для изменения значения переменной-члена.

Константные методы классов


Теперь рассмотрим следующую строчку кода:

Удивительно, но это также вызовет ошибку компиляции, хотя getValue() не делает ничего для изменения переменной-члена! Оказывается, константные объекты класса могут явно вызывать только константные методы класса, а getValue() не указан как константный метод. Константный метод — это метод, который гарантирует, что не будет изменять объект или вызывать неконстантные методы класса (поскольку они могут изменить объект).

Чтобы сделать getValue() константным, нужно просто добавить ключевое слово const к прототипу функции после списка параметров, но перед телом функции:

Теперь getValue() является константным методом, что означает, что мы можем вызывать его через любой константный объект.

Для методов, определённых вне тела класса, ключевое слово const должно использоваться как в прототипе функции в теле класса, так и в определении функции:

Кроме того, любой константный метод, который пытается изменить переменную-член или вызвать неконстантный метод класса также приведёт к ошибке компиляции. Например:

В этом примере resetValue() был установлен константным, но он пытается изменить значение m_value. Это вызовет ошибку компиляции.

Обратите внимание, конструкторы не могут быть константными. Это связано с тем, что они должны иметь возможность инициализировать переменные-члены класса, а константный конструктор этого не может сделать. Следовательно, в С++ константные конструкторы запрещены.

Стоит отметить, что константный объект класса может вызывать конструктор, который будет инициализировать все, некоторые или ни одну из переменных-членов!

Правило: Делайте все ваши методы, которые не изменяют данные объекта класса, константными.

Константные ссылки и классы

Ещё одним способом создания константных объектов является передача объектов в функцию по константной ссылке.

В уроке №98 мы рассмотрели преимущества передачи аргументов по константной ссылке, нежели по значению. Вкратце: передача аргументов по значению создаёт копию значения (что является медленным процессом), большую часть времени нам не нужна копия, а ссылка уже указывает на исходный аргумент и является более эффективной, так как избегает создания и использование ненужной копии. Мы обычно делаем ссылку константной для гарантии того, что функция не изменит значение аргумента и позволит функции работать с r-values (например, литералами).

Можете ли вы определить, что не так со следующим кодом?

Ответ заключается в том, что внутри функции printDate(), объект date рассматривается как константный. И через этот константный date мы вызываем функции getDay(), getMonth() и getYear(), которые являются неконстантными. Поскольку мы не можем вызывать неконстантные методы через константные объекты, то здесь мы получим ошибку компиляции.

Решение простое: сделать getDay(), getMonth() и getYear() константными:

Теперь в функции printDate() константный date сможет вызывать getDay(), getMonth() и getYear().

Перегрузка константных и неконстантных функций


Хотя это делается не очень часто, но функцию можно перегрузить таким образом, чтобы иметь константную и неконстантную версии одной и той же функции:

Константная версия функции будет вызываться для константных объектов, а неконстантная версия будет вызываться для неконстантных объектов:

Перегрузка метода и разделение его на константную и неконстантную версии обычно выполняется, когда возвращаемое значение должно отличаться по константности (когда требуется константа, а когда — нет). В примере выше неконстантная версия getValue() будет работать только с неконстантными объектами, но эта версия более гибкая, так как мы можем использовать её как для чтения, так и для записи m_value (что мы, собственно, и делаем, присваивая строку Hello!).

Но, когда мы не изменяем данные объекта класса, то тогда вызывается константная версия getValue().

Заключение

Любой метод, который не изменяет данные объекта класса, должен быть const!


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (50 оценок, среднее: 4,88 из 5)
Загрузка...

Комментариев: 2

  1. Аватар Александр:

    Спасибо! нашлось другое — более симпатичное решение одной проблемки. Примерная суть:

    допустим у меня есть метод write для объекта с заголовком:

    и возвратом return *this (удобно так иногда делать — можно цепочкой точечек пользоваться)

    Его можно использовать для перегрузки оператора << :

    или

    Первое работает и для констант, но выполняет копирование, что не есть гуд.
    написать:

    нельзя — работать не будет. read требует "живого" объкта
    Полностью менять метод read — нехорошо. Не совсем понятно становится, почему именно этот метод не возвращает this. Создавать метод с другим именем или перегружать с управляющими параметрами тоже не айс. А вот сделать константную перегрузку — самое оно

    еще раз спасибо 🙂

    1. Аватар Александр:

      Мало того! можно не дублировать код функции. Пусть определена:

      Тогда неконстантная перегрузка может быть такой:

      🙂
      Вопрос: можно ли специфицировать вызов именно константной версии как-то проще, чем в коде выше?

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *