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

  Юрий  | 

  |

  Обновл. 13 Сен 2021  | 

 80979

 ǀ   13 

На уроке №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 (297 оценок, среднее: 4,90 из 5)
Загрузка...

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

  1. Борис:

    В последнем примере строка

    Вот никогда бы не подумал, что таким извращённым способом можно присвоить значение внутренней переменной, которая ещё и private. И этот геттер возвращает m_value по значению, то есть копию переменной. Почему это тогда внутренняя переменная объекта должна измениться при присваивании чего-то этой копии? Да и само присваивание значения вызову метода — тоже ломает мозг. Не ожидал, что вызов метода может быть выражением l-value. Неужели это где-то используется?

    1. Михаил:

      Этот геттер возвращает не по значению, а неконстантную ссылку (следовательно lvalue):

    2. Максим:

      Вот так геттер стал сеттером.
      Трансгеттер!
      Меня к такому жизнь не готовила.
      Мои полномочия все…

      1. Vitalt1158:

        хех, орнул) Поэтому все методы get надо делать const)

  2. Алексей:

    Хочу просто уточнить.

    Как я вижу тут выходит, что идет инициализация только не в переменную. В константный метод геттер инициализируется возврат, то ли вычислений, то ли еще каких данных.

    Грубо говоря мы предоставили файл, который используется, не меняя его, инициализировали, в памяти 80Кб, просто указываем на это "значение" не теряя больше память.

    Как-то так, я хочу понимать как работают сложные приложения. Игры те же, на плюсах написанное всегда очень хорошо работает и требования всегда снижены.

  3. somebox:

    Когда-нибудь такие конструкции меня добьют 🙂

    1. Олег:

      Это точно ))), когда приходишь с логикой пхп, например. То эти плюшки, ломают мозг )))

      1. Валера:

        Я вообще с JS пришел. Капец сложно переучится )

        1. Олег:

          Тут просто своя логика, если читать и перечитывать, то все понятно, я сейчас ушел в JS )). После C++ становятся многие вещи более понятными, например ссылки на функции и иногда не хватает типизации. Вообще базу на плюсах полезно пройти, многое потом становится понятным, ты начинаешь понимать как под капотом.

    2. meowpowww:

      Поясните, пожалуйста, почему нужно еще спереди указать const? Как я понял это связано с тем, что функция возвращает ссылку.

      1. Mike WIshnevski:

        Попробуй не указывать const вначале функции. Исходя их моих личных тестов, перегрузка работает с const в конце, а const спереди ни на что не влияет. Имхо.

  4. Александр:

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

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

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

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

    или

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

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

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

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

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

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

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

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

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