Конструкторы
Понятие конструктора существует во многих языках программирования и также является абстракцией, основное предназначение которой – абстрагироваться от процедуры конструирования объекта в программе. Если пробежаться глазами по примерам из предыдущих частей, можно заметить постепенный переход от конструирования объектов типа Здесь есть несколько тонкостей. Во-первых, было бы неплохо добиться того, чтобы объекты можно было создавать без явного вызова специальной функции. Например, если пользователь не знает, какие первоначальные значения для числителя и знаменателя задать, конструктор мог бы определять их самостоятельно. Во-вторых, было бы удобно создавать новые объекты, используя уже существующие объекты напрямую, без явного вызова геттеров и последующей передачи их в конструктор, как было сделано в одном из примеров выше:
Было бы логичней если бы оно выглядело, например, так:
В этом случае намерение данной инструкции выглядит более явным: вызвать специальную функцию, которая носит имя того типа данных, который будет использоваться для создания нового объекта, при этом новый объект будет создан на базе существующего.
Это подразумевает установление для всех полей нового объекта значений на основании величин, хранящихся в соответствующих полях аргумента. Используя последний пример, новый объект, который будет создан в результате этой операции, в своем поле
Существуют и другие типы конструкторов. Например, вы могли бы написать такую функцию, которая создает объект типа
Для всего этого можно сделать соответствующие функции внутри заданного класса, дав этим функциям названия, которые указывают на их предназначение. И в некоторых языках программирования это пришлось бы сделать, так как они не позволяют создавать больше одного конструктора. В С++ конструкторов может быть сколько угодно, отличаться они должны только в том, какие аргументы они принимают. Например, ниже можно посмотреть на код, который конвертирует функцию
Пример выше содержит несколько синтаксических приемов, которые до сих пор в этом тексте не встречались. В соответствии с правилами С++ у конструкторов есть ряд неявных свойств, о которых следует знать перед тем, как начать ими пользоваться.
Первое, что бросается в глаза – отсутствие типа данных перед именем этой функции. Второе – это некий указатель
Первая особенность заключается в том, что на уровне кода пользователя конструкторы имеют сокращенную форму. В первую очередь, это выражается в том, что у них, как и у всех функций, находящихся внутри класса, есть скрытый первый аргумент. У этого аргумента есть тип и название (речь идет о ключевом слове
В этом фрагменте кода нет ничего загадочного. В функцию передается адрес, по которому хранятся данные рационального числа, и посредством этого адреса функция может изменять эти данные. Единственный вопрос, который закономерно возникает: как передать этот адрес в функцию, которая скрывает аргумент
Здесь стоит вспомнить что было сказано в предыдущей части о вызове функций, которые объявлены внутри класса. Как вы уже знаете, эти функции вызываются следующим образом (где
Вызов такой функции возможен только посредством существующего объекта, следовательно функция изменяет состояние объекта, который ее вызывает (при этом он явно не обозначается как один из аргументов), из чего можно сделать вывод, что объект туда передан неявно.
Этот объект передается как указатель, для удобства названный Для конструкторов это тоже верно, то есть, они являются специальными методами. Главная же особенность конструкторов заключается в том, что они вызываются автоматически во время создания объекта в программе. Иначе говоря, каждая переменная соответствующего типа, объявленная в программе, в момент своей инициализации вызовет подходящий конструктор. Это также верно для классов, в которых не объявлено ни одного явного конструктора. В С++ компилятор автоматически генерирует конструкторы в меру своих способностей в случае их отсутствия. На практике на это полагаться не рекомендуется, поэтому если ваш конструктор должен соблюдать определенный набор правил, его надо задавать явно.
Конкретней, для типа
Конструкторы по умолчанию выполняют действия по инициализации полей класса в тех случаях, когда для этого нет конкретных данных. В данном примере конструктор по умолчанию будет сгенерирован автоматически. Так как внутренние поля представлены примитивным типом Первый способ заключается в предварительной инициализации полей внутри класса.
Используя инициализаторы (фигурные скобки после имени поля с данными) можно задать первоначальные значения для всех переменных такого типа. Это делается еще до вызова конструктора. Так можно гарантировать правильные значения для всех полей во избежание странных ошибок в дальнейшем. Другой способ заключается в задании значений по умолчанию для одного из конструкторов с параметрами.
Строка
То есть, в данном случае компилятор считает, что конструктор с параметрами одновременно является и конструктором по умолчанию. Если вызвать этот конструктор с конкретными значениями, они заменят аргументы по умолчанию при вызове функции, если же не передавать ничего, этот конструктор все равно будет вызван, но аргументы будут иметь значения по умолчанию, определенные в заголовке функции. Третий способ, который мы рассмотрим – это запрет на конструирование объектов заданного типа без параметров. Если в вашей программе нет ситуаций, в которых рациональное число нужно было бы создавать без конкретных значений, лучше себя обезопасить от случайностей, указав на это в классе, чтобы компилятор мог распознавать все попытки так сделать.
Здесь появляется новое ключевое слово Следующий важный конструктор, который стоит реализовать – это конструктор копирования. Он нужен для того, чтобы в программе можно было создавать новые переменные как копии уже существующих переменных этого же типа.
Здесь надо быть очень внимательным. Как и в случае с конструктором по умолчанию, компилятор генерирует конструктор копирования в определенных ситуациях, поэтому пример кода выше может быть вполне рабочим. Это происходит потому, что все поля в классе
Как вы уже знаете, в конструктор передается аргумент для скрытого параметра
То есть, в случаях, когда достаточно просто скопировать данные из одной переменной в другую, язык позволяет сделать это до вызова тела конструктора. Упражнения1
Используя примеры из этой части необходимо написать конструктор, который создает объект типа 2
Конструктор переноса является еще одним “каноническим конструктором”, который в С++ принято реализовывать из соображений быстродействия программ. Останавливаться на нем очень подробно сейчас не стоит, но познакомиться с принципами его работы полезно. Работает он следующим образом. Для значений особого типа такой конструктор не копирует передаваемый ему объект, а “крадет” существующие в памяти значения, переписывая адреса этих значений на свой объект. Таким образом переданный в этот конструктор объект остается без действительных значений, так как все они были перенесены в другой объект. В зависимости от конкретной реализации языка С++ это может происходить автоматически2. В большинстве же случаев программисты самостоятельно отмечают переменные, которые они хотят перенести. Такие переменные принято называть ссылками на R-значения3 и в коде отмечать как В одной из следующих глав будет рассмотрен более конкретный пример того, зачем такой конструктор нужен на практике. Самое главное, не зацикливайтесь на этих нюансах если что-то пока не понятно. Задача данного текста заключается не в том, чтобы объяснить особенности реализации языка С++, который используется только в ознакомительных целях. Загвоздка же в том, что ссылки на R-значения являются способом реализации важных абстрактных идей, который выбрали разработчики С++, и вам следует иметь это ввиду. Попробуйте написать такой конструктор самостоятельно, а затем сверьтесь с примером из соответствующего раздела. Сноски1Ссылки в С++ отличаются от указателей в С тем, что они уже разыменованы в момент инициализации; если для какого-то 2Компилятору позволяется самому выбирать между копированием и переносом в ситуациях, когда переменная не отмечена ключевым словом 3Это название исторически закрепилось за анонимными значениями в коде, так как они всегда находятся справа от оператора присваивания (отсюда и название, right-hand value – R-value). Примером такого значения может быть любое константное число (
Copyright © 2023 Брынзан, Л. В. |