Что называется конструктором в программировании
Урок №116. Конструкторы
На этом уроке мы рассмотрим конструкторы в языке С++.
Конструкторы
Когда все члены класса (или структуры) являются открытыми, то мы можем инициализировать класс (или структуру) напрямую, используя список инициализаторов или uniform-инициализацию (в C++11):
Однако, как только мы сделаем какие-либо переменные-члены класса закрытыми, то больше не сможем инициализировать их напрямую. Здесь есть смысл: если вы не можете напрямую обращаться к переменной (потому что она закрыта), то вы и не должны иметь возможность напрямую её инициализировать.
Как тогда инициализировать класс с закрытыми переменными-членами? Использовать конструкторы.
Конструктор — это особый тип метода класса, который автоматически вызывается при создании объекта этого же класса. Конструкторы обычно используются для инициализации переменных-членов класса значениями, которые предоставлены по умолчанию/пользователем, или для выполнения любых шагов настройки, необходимых для используемого класса (например, открыть определенный файл или базу данных).
В отличие от обычных методов, конструкторы имеют определенные правила их именования:
конструкторы всегда должны иметь то же имя, что и класс (учитываются верхний и нижний регистры);
Обратите внимание, конструкторы предназначены только для выполнения инициализации. Не следует пытаться вызывать конструктор для повторной инициализации существующего объекта. Хотя это может скомпилироваться без ошибок, результаты могут получиться неожиданные (компилятор создаст временный объект, а затем удалит его).
Конструкторы по умолчанию
Конструктор, который не имеет параметров (или содержит параметры, которые все имеют значения по умолчанию), называется конструктором по умолчанию. Он вызывается, если пользователем не указаны значения для инициализации. Например:
Конструктор (программирование)
Конструктор схож с методом, но отличается от метода тем, что не имеет явным образом определённого типа возвращаемых данных, не наследуется, и обычно имеет различные правила для рассматриваемых модификаторов. Конструкторы часто выделяются наличием одинакового имени с именем класса, в котором объявляется. Их задача — инициализировать члены объекта и определить инвариант класса, сообщив в случае некорректности инварианта. Корректно написанный конструктор оставит объект в «правильном» состоянии. Неизменяемые объекты тоже должны быть проинициализированы конструктором.
Термин «конструктор» также используется для обозначения одного из тегов, описывающих данные в алгебраическом типе данных. Это использование несколько отличается от описываемого в статье. Для дополнительной информации смотрите Алгебраический тип данных.
В большинстве языков конструктор может быть перегружен, что позволяет использовать несколько конструкторов в одном классе, причём каждый конструктор может иметь различные параметры.
Содержание
Назначение конструктора
Одна из ключевых особенностей ООП — инкапсуляция: внутренние поля объекта напрямую недоступны, и пользователь может работать с объектом только как с единым целым, через открытые ( public ) методы. Каждый метод, в идеале, должен быть устроен так, чтобы объект, находящийся в «допустимом» состоянии (то есть когда выполняется инвариант класса), после вызова метода также оказался в допустимом состоянии. И первая задача конструктора — перевести поля объекта в такое состояние.
Но удобнее открытие файла сделать в конструкторе: [1]
Виды конструкторов
Некоторые языки программирования различают несколько особых типов конструкторов:
Конструктор по умолчанию
Конструктор не имеющий обязательных аргументов. Используется при создании массивов объектов, вызываясь для создания каждого экземпляра. В отсутствие явно заданного конструктора по умолчанию его код генерируется компилятором (что на исходном тексте, естественно, не отражается).
Конструктор копирования
Конструктор, аргументом которого является ссылка на объект того же класса. Применяется в C++ для передачи объектов в функции по значению.
Конструктор копирования в основном необходим, когда объект имеет указатели на объекты выделенные в куче. Если программист не создаёт конструктор копирования, то компилятор создаст неявный конструктор копирования, который копирует указатели как есть, то есть фактическое копирование данных не происходит и два объекта ссылаются на одни и те же данные в куче. Соответственно попытка изменения «копии» повредит оригинал, а вызов деструктора для одного из этих объектов при последующем использовании другого приведёт к обращению в область памяти, уже не принадлежащую программе.
Аргумент должен передаваться именно по ссылке, а не по значению. Это вытекает из коллизии: при передаче объекта по значению (в частности, для вызова конструктора) требуется скопировать объект. Но для того, чтобы скопировать объект, необходимо вызвать конструктор копирования.
Конструктор преобразования
Конструктор, принимающий один аргумент. Задаёт преобразование типа своего аргумента в тип конструктора. Такое преобразование типа неявно применяется только если оно уникально.
Виртуальный конструктор
Конструктор не бывает виртуальным в смысле виртуального метода — для того, чтобы механизм виртуальных методов работал, нужно запустить конструктор, который автоматически настроит таблицу виртуальных методов данного объекта.
«Виртуальными конструкторами» называют похожий, но другой механизм, присутствующий в некоторых языках — например, он есть в Delphi, но нет в C++ и Java. Этот механизм позволяет создать объект любого заранее неизвестного класса при двух условиях:
является некорректным — директива reintroduce разорвала цепочку переопределения виртуального метода, и в действительности будет вызван конструктор TMotorcycle.Create (а значит, будет создан мотоцикл, а не мопед!)
Синтаксис
Имя конструктора должно совпадать с именем класса. Допускается использовать несколько конструкторов с одинаковым именем, но различными параметрами.
Пример
Python
Пример
Delphi
Пример
Некоторые отличия между конструкторами и другими методами Java:
Пример
JavaScript
Пример
Пример
Пример
Эйфель
В Эйфеле подпрограммы, которые инициализируют объекты, называются процедурами создания. Процедуры создания в чём-то подобны конструкторам и в чём-то отличаются. Они имеют следующие характеристики:
Пример
ColdFusion
Пример
Необходимо отметить, что в ColdFusion не существует метода-конструктора. Широкое распространение среди сообщества программистов на ColdFusion получил способ вызова метода ‘ init ‘, выступающего в качестве псевдоконструктора.
Пример
Тем не менее, конструктор в PHP версии 4 (и ранее) — метод класса с именем этого же класса.
Пример
В Perl конструктор должен применить функцию bless к некой переменной (обычно ссылке на хеш):
Но это минимальный базовый вариант, есть множество более продвинутых способов, начиная от use fields и заканчивая Moose.
Упрощенные конструкторы (с псевдокодом)
Конструкторы всегда являются частью реализации классов. Класс (в программировании) описывает спецификации основных характеристик набора объектов, являющихся членами класса, а не отдельные характеристики какого-либо объекта из них. Рассмотрим простую аналогию. Возьмем в качестве примера набор (или класс, используя его более общее значение) учеников некоторой школы. Таким образом мы имеем:
Тем не менее, класс Student — всего лишь общий шаблон (прототип) наших школьников. Для его использования программист создает каждого школьника в виде объекта или сущности (реализации) класса. Этот объект является тем реальным фрагментом данных в памяти, чьи размер, шаблон, характеристики и (в некоторой мере) поведение определяются описанием класса. Обычный способ создания объектов — вызов конструктора (классы в общем случае могут иметь отдельные конструкторы). Например,
Конструкторы (C++)
При необходимости конструкторы могут получить список инициализации элемента. Это более эффективный способ инициализации членов класса, чем назначение значений в теле конструктора. В следующем примере показан класс Box с тремя перегруженными конструкторами. Последние два списка инициализации элементов use:
При объявлении экземпляра класса компилятор выбирает конструктор для вызова на основе правил разрешения перегрузки:
Списки инициализаторов членов
Использование списка инициализаторов членов предпочтительнее, чем назначение значений в теле конструктора, так как он непосредственно Инициализирует элемент. В следующем примере показан список инициализаторов членов, состоящий из всех выражений идентификаторов (аргументов) после двоеточия:
Идентификатор должен ссылаться на член класса; он инициализируется значением аргумента. Аргумент может быть одним из параметров конструктора, вызовом функции или std:: initializer_list T >.
const члены и члены ссылочного типа должны быть инициализированы в списке инициализаторов членов.
В списке инициализаторов должны быть сделаны вызовы для параметризованных конструкторов базового класса, чтобы гарантировать, что базовый класс полностью инициализирован до выполнения производного конструктора.
Конструкторы по умолчанию
Конструкторы по умолчанию обычно не имеют параметров, но могут иметь параметры со значениями по умолчанию.
Конструкторы по умолчанию являются одной из специальных функций элементов. Если в классе не объявлен ни один конструктор, компилятор предоставляет неявный inline конструктор по умолчанию.
Если вы полагаетесь на неявный конструктор по умолчанию, обязательно инициализируйте элементы в определении класса, как показано в предыдущем примере. Без этих инициализаторов члены будут неинициализированы, а вызов Volume () создаст значение мусора. Как правило, рекомендуется инициализировать элементы таким образом, даже если не полагается на неявный конструктор по умолчанию.
Можно запретить компилятору создавать неявный конструктор по умолчанию, определив его как Удаленный:
Созданный компилятором конструктор по умолчанию будет определен как удаленный, если какие-либо члены класса не являются конструируемым по умолчанию. Например, все члены типа класса и их члены типа класса должны иметь доступ к конструктору по умолчанию и деструкторам, которые доступны. Все элементы данных ссылочного типа, а также const члены класса должны иметь инициализатор членов по умолчанию.
При вызове конструктора, созданного компилятором по умолчанию, и попытке использовать круглые скобки выдается предупреждение:
Это пример проблемы Most Vexing Parse (наиболее неоднозначного анализа). Поскольку выражение примера можно интерпретировать как объявление функции или как вызов конструктора по умолчанию и в связи с тем, что средства синтаксического анализа C++ отдают предпочтение объявлениям перед другими действиями, данное выражение обрабатывается как объявление функции. Дополнительные сведения см. в разделе досадной Parse.
В случае явного объявления конструкторов компилятор не предоставляет конструктор по умолчанию:
Если у класса нет конструктора по умолчанию, массив объектов этого класса не может быть создан только с помощью синтаксиса двух квадратных скобок. Например, в представленном выше блоке кода массив Boxes не может быть объявлен следующим образом:
Однако можно использовать набор списков инициализаторов для инициализации массива объектов Box:
Дополнительные сведения см. в разделе инициализаторы.
Конструкторы копии
Конструктор копии Инициализирует объект, копируя значения элементов из объекта того же типа. Если все члены класса являются простыми типами, такими как скалярные значения, конструктор копий, созданный компилятором, достаточно, и вам не нужно определять собственный. Если для класса требуется более сложная инициализация, необходимо реализовать пользовательский конструктор копии. Например, если член класса является указателем, необходимо определить конструктор копии, чтобы выделить новую память и скопировать значения из объекта, указывающего на другой объект. Созданный компилятором конструктор копий просто копирует указатель, так что новый указатель по-прежнему указывает на расположение в памяти другого.
Конструктор копии может иметь одну из следующих сигнатур:
При определении конструктора копии необходимо также определить оператор присваивания копирования (=). Дополнительные сведения см. в разделе конструкторы присваивания и копирования и операторы присваивания копирования.
Вы можете запретить копирование объекта, определив конструктор копии как удаленный:
Попытка копирования объекта приводит к ошибке C2280: попытка ссылки на удаленную функцию.
Конструкторы перемещения
Конструктор перемещения — это специальная функция-член, которая перемещает владение данными существующего объекта в новую переменную без копирования исходных данных. Он принимает в качестве первого параметра ссылку rvalue, а все дополнительные параметры должны иметь значения по умолчанию. Конструкторы перемещения могут значительно повысить эффективность программы при передаче больших объектов.
Если класс не определяет конструктор перемещения, компилятор создает неявный экземпляр, если не существует объявленного пользователем конструктора копии, оператора присваивания копирования, оператора присваивания перемещения или деструктора. Если явный или неявный конструктор перемещения не определен, операции, в которых в противном случае использовался конструктор перемещения, используют вместо этого конструктор копий. Если класс объявляет конструктор перемещения или оператор присваивания перемещения, неявно объявленный конструктор копии определяется как удаленный.
Неявно объявленный конструктор перемещения определяется как удаленный, если какие-либо члены, являющиеся типами классов, не имеют деструктора или компилятор не может определить, какой конструктор использовать для операции перемещения.
Дополнительные сведения о написании нетривиальных конструкторов перемещения см. в разделе конструкторы перемещения и операторы присваивания перемещения (C++).
Явно заданные по умолчанию и удаленные конструкторы
конструкторы constexpr
Конструкторы списка инициализаторов
Затем создайте объекты Box следующим образом:
Явные конструкторы
Если у класса имеется конструктор с одним параметром, или у всех параметров, кроме одного, имеются значения по умолчанию, тип параметра можно неявно преобразовать в тип класса. Например, если у класса Box имеется конструктор, подобный следующему:
то возможно инициализировать объект Box следующим образом:
Или передать целое значение функции, принимающей объект Box:
В некоторых случаях подобные преобразования могут быть полезны, однако чаще всего они могут привести к незаметным, но серьезным ошибкам в вашем коде. В качестве общего правила следует использовать explicit ключевое слово в конструкторе (и определяемых пользователем операторах) для предотвращения такого рода неявного преобразования типов:
Порядок создания
Конструктор выполняет свою работу в следующем порядке.
Вызывает конструкторы базовых классов и членов в порядке объявления.
Если класс является производным от виртуальных базовых классов, конструктор инициализирует указатели виртуальных базовых классов объекта.
Если класс имеет или наследует виртуальные функции, конструктор инициализирует указатели виртуальных функций объекта. Указатели виртуальных функций указывают на таблицу виртуальных функций класса, чтобы обеспечить правильную привязку вызовов виртуальных функций к коду.
Выполняет весь код в теле функции.
В следующем примере показан порядок, в котором конструкторы базовых классов и членов вызываются в конструкторе для производного класса. Сначала вызывается конструктор базового класса, затем инициализируются члены базового класса в порядке их появления в объявлении класса. После этого вызывается конструктор производного класса.
Выходные данные будут выглядеть следующим образом.
Если базовый класс не имеет конструктор по умолчанию, в конструкторе производного класса необходимо указать параметры конструктора базового класса.
Если конструктор создает исключение, то удаление выполняется в порядке, обратном созданию.
Отменяется код в теле функции конструктора.
Объекты базовых классов и объекты-члены удаляются в порядке, обратном объявлению.
Если конструктор не является делегирующим, удаляются все полностью созданные объекты базовых классов и объекты-члены. Однако поскольку сам объект создан не полностью, деструктор не выполняется.
Производные конструкторы и расширенная агрегатная инициализация
если конструктор базового класса не является открытым, но доступен для производного класса, то нельзя использовать пустые фигурные скобки для инициализации объекта производного типа в /std:c++17 режиме, а затем в Visual Studio 2017 и более поздних версиях.
В следующем примере показана соответствующая реакция на событие в C++14:
в следующем примере показано поведение c++ 17 в Visual Studio 2017 и более поздних версиях в /std:c++17 режиме:
Конструкторы для классов с несколькими наследованиями
Если класс является производным от нескольких базовых классов, конструкторы базовых классов вызываются в том порядке, в котором они перечислены в объявлении производного класса.
Должны выводиться следующие выходные данные:
Делегирующие конструкторы
Делегирующий конструктор вызывает другой конструктор в том же классе для выполнения некоторой работы по инициализации. Это полезно, если у вас есть несколько конструкторов, которые должны выполнять одинаковую работу. Можно написать основную логику в одном конструкторе и вызвать ее из других. В следующем тривиальном примере Box (int) делегирует свою работу Box (int, int, int):
Объект, созданный конструкторами, полностью инициализируется сразу после выполнения любого конструктора. Дополнительные сведения см. в разделе Делегирование конструкторов.
Наследование конструкторов (C++11)
Производный класс может наследовать конструкторы от прямого базового класса с помощью объявления, using как показано в следующем примере:
Visual Studio 2017 и более поздних версий: инструкция в /std:c++17 режиме и более поздних версиях предоставляет все конструкторы из базового класса, за исключением тех, которые имеют идентичную сигнатуру для конструкторов в производном классе. Обычно, если в производном классе не объявляются новые данные-члены или конструкторы, оптимальным решением будет использовать наследуемые конструкторы.
Шаблон класса может наследовать все конструкторы от аргумента типа, если этот тип определяет базовый класс:
Производный класс не может наследовать от нескольких базовых классов, если у этих базовых классов есть конструкторы с идентичными сигнатурами.
Конструкторы и составные классы
Классы, содержащие члены типа класса, называются составными классами. При создании члена типа класса составного класса конструктор вызывается перед собственным конструктором класса. Если у содержащегося класса нет конструктора по умолчанию, необходимо использовать список инициализации в конструкторе составного класса. В предыдущем примере StorageBox при присвоении типу переменной-члена m_label нового класса Label необходимо вызвать конструктор базового класса и инициализировать переменную m_label в конструкторе StorageBox :
Конструктор (программирование)
Конструктор схож с методом, но отличается от метода тем, что не имеет явным образом определённого типа возвращаемых данных, не наследуется, и обычно имеет различные правила для рассматриваемых модификаторов. Конструкторы часто выделяются наличием одинакового имени с именем класса, в котором объявляется. Их задача — инициализировать члены объекта и определить инвариант класса, сообщив в случае некорректности инварианта. Корректно написанный конструктор оставит объект в ‘правильном’ статусе. Неизменяемые объекты тоже должны быть проинициализированы конструктором.
Термин конструктор также используется для обозначения одного из тегов, описывающих данные в алгебраическом типе данных. Это использование несколько отличается от описываемого в статье. Для дополнительной информации смотрите Алгебраический тип данных.
В большинстве языков конструктор может быть перегружен, что позволяет использовать несколько конструкторов в одном классе, причём каждый конструктор может иметь различные параметры. Некоторые языки различают несколько особых типов конструкторов:
Содержание
Виды конструкторов
Конструктор по умолчанию
Конструктор не имеющий обязательных аргументов. Используется при создании массивов объектов, вызываясь для создания каждого экземпляра. В отсутствие явно заданного конструктора по умолчанию его код генерируется компилятором (что на исходном тексте, естественно, не отражается).
Конструктор копирования
Конструктор, аргументом которого является ссылка на объект того же класса. Применяется в C++ для передачи объектов в функции по значению.
Конструктор копирования нужен, например, если для хранения данных объекта требуется дополнительно выделяемая память. Если его не будет, то конструктором копирования (сгенерированным компилятором) будут скопированы указатели, адресующие данные прежнего объекта (без выделения новой памяти). Соответственно попытка изменения «копии» повредит оригинал, а вызов деструктора для одного из этих объектов при последующем использовании другого приведёт к обращению в область памяти, уже не принадлежащую программе.
Аргумент должен передаваться именно по ссылке, а не по значению. Это вытекает из коллизии: при передаче объекта по значению (в частности, для вызова конструктора) требуется скопировать объект. Но для того, чтобы скопировать объект, необходимо вызвать конструктор копирования.
Конструктор преобразования
Конструктор, принимающий один агрумент. Задаёт преобразование типа своего аргумента в тип конструктора. Такое преобразование типа неявно применяется только если оно уникально.
Виртуальный конструктор
Конструктор не бывает виртуальным в смысле виртуального метода — для того, чтобы механизм виртуальных методов работал, нужно запустить конструктор, который автоматически настроит таблицу виртуальных методов данного объекта.
«Виртуальными конструкторами» называют похожий, но другой механизм, присутствующий в некоторых языках — например, он есть в Delphi, но нет в C++ и Java. Этот механизм позволяет создать объект любого заранее неизвестного класса при двух условиях:
является некорректным — директива reintroduce разорвала цепочку переопределения виртуального метода, и в действительности будет вызван конструктор TMotorcycle.Create (а значит, будет создан мотоцикл, а не мопед!)
Синтаксис
Имя конструктора должно совпадать с именем класса. Допускается использовать несколько конструкторов с одинаковым именем, но различными параметрами.
Python
Пример
Delphi
Пример
Некоторые отличия между конструкторами и другими методами Java:
Пример
JavaScript
В JavaScript в качестве конструктора выступает обычная функция. Используется в качестве операнда оператора new. Использует this для обращения к созданному оператором new объекту.
Пример
Пример
Пример
Eiffel
В Эйфеле подпрограммы, которые инициализируют объекты, называются процедурами создания. Процедуры создания в чём-то подобны конструкторам и в чём-то отличаются. Они имеют следующие характеристики:
Пример
ColdFusion
Пример
Необходимо отметить, что в ColdFusion не существует метода-конструктора. Широкое распространение среди сообщества программистов на ColdFusion получил способ вызова метода ‘ init ‘, выступающего в качестве псевдоконструктора.
Пример
Тем не менее, конструктор в PHP версии 4 (и ранее) — метод класса с именем этого же класса.
Упрощенные конструкторы (с псевдокодом)
Конструкторы всегда являются частью реализации классов. Класс (в программировании) описывает спецификации основных характеристик набора объектов, являющихся членами класса, а не отдельные характеристики какого-либо объекта из них. Рассмотрим простую аналогию. Возьмем в качестве примера набор (или класс, используя его более общее значение) учеников некоторой школы. Таким образом мы имеем:
Тем не менее, класс Student — всего лишь общий шаблон (прототип) наших школьников. Для его использования программист создает каждого школьника в виде объекта или сущности (реализации) класса. Этот объект является тем реальным фрагментом данных в памяти, чьи размер, шаблон, характеристики и (в некоторой мере) поведение определяются описанием класса. Обычный способ создания объектов — вызов конструктора (классы в общем случае могут иметь отдельные конструкторы). Например,