Assembly tool included в конструкторе для чего

Обновлено: 16.05.2024

В последних двух уроках мы изучили основы наследования в C++ и порядок инициализации производных классов. В этом уроке мы более подробно рассмотрим роль конструкторов в инициализации производных классов. Для этого мы продолжим использовать простые классы Base и Derived , которые мы разработали в предыдущем уроке:

В случае классов, не являющихся производными, конструкторам нужно беспокоиться только о своих членах. Например, рассмотрим Base . Мы можем создать объект Base следующим образом:

Вот что на самом деле происходит при создании экземпляра Base :

  1. выделяется память для Base ;
  2. вызывается соответствующий конструктор Base ;
  3. список инициализации инициализирует переменные;
  4. выполняется тело конструктора;
  5. управление возвращается вызывающей функции.

Всё довольно просто. С производными классами всё немного сложнее:

Вот что на самом деле происходит при создании экземпляра Derived :

  1. выделяется память для Derived (достаточная и для части Base , и для части Derived );
  2. вызывается соответствующий конструктор Derived ;
  3. сначала создается объект Base с использованием соответствующего конструктора Base . Если конструктор Base не указан, будет использоваться конструктор по умолчанию;
  4. список инициализации инициализирует переменные;
  5. выполняется тело конструктора;
  6. управление возвращается вызывающей функции.

Единственное реальное различие между этим случаем и случаем без наследования состоит в том, что прежде, чем конструктор Derived сможет сделать что-либо существенное, сначала вызывается конструктор Base . Конструктор Base создает часть Base объекта, управление возвращается конструктору Derived , и конструктору Derived разрешается завершить свою работу.

Инициализация членов базового класса

Один из текущих недостатков нашего класса Derived в том виде, в котором он написан, заключается в том, что при создании объекта Derived нет возможности инициализировать m_id . Что, если при создании объекта Derived мы хотим установить и m_cost (из части Derived объекта), и m_id (из части Base объекта)?

Начинающие программисты часто пытаются решить эту проблему следующим образом:

Это хорошая попытка и почти правильная идея. Нам обязательно нужно добавить в наш конструктор еще один параметр, иначе C++ не сможет узнать, каким значением мы хотим инициализировать m_id .

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

Почему C++ так делает? Ответ связан с константными и ссылочными переменными. Подумайте, что бы произошло, если бы m_id был const . Поскольку константные переменные должны быть инициализированы значением во время создания, конструктор базового класса при создании переменной должен установить ее значение. Однако списки инициализации конструкторов производного класса выполняются после завершения работы конструктора базового класса. А если у каждого производного класса будет возможность инициализировать эту переменную, он потенциально сможет изменить ее значение! Ограничивая инициализацию переменных конструктором класса, к которому эти переменные принадлежат, C++ гарантирует, что все переменные инициализируются только один раз.

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

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

Хотя в данном случае это действительно работает, это не сработало бы, если бы m_id был константой или ссылкой (потому что константные значения и ссылки должны быть инициализированы в списке инициализации конструктора). Это также неэффективно, потому что переменной m_id значение присваивается дважды: один раз в списке инициализации конструктора класса Base , а затем снова в теле конструктора класса Derived . И, наконец, что, если классу Base потребовался бы доступ к этому значению во время создания? У него нет возможности получить доступ к этому значению, поскольку оно не устанавливается до тех пор, пока не будет выполнен конструктор Derived (что происходит в последнюю очередь).

Итак, как правильно инициализировать m_id при создании объекта класса Derived ?

До сих пор во всех примерах, когда мы создавали экземпляр объекта класса Derived , часть Base класса создавалась с использованием конструктора Base по умолчанию. Почему он всегда использовал конструктор Base по умолчанию? Потому что мы никогда не указывали иное!

К счастью, C++ дает нам возможность явно выбирать, какой конструктор класса Base будет вызываться! Для этого просто добавьте вызов конструктора класса Base в список инициализации класса Derived :

Теперь, когда мы выполняем этот код:

Конструктор базового класса Base(int) будет использоваться для инициализации m_id значением 5, а конструктор производного класса будет использоваться для инициализации m_cost значением 1.3!

Таким образом, программа напечатает:

Вот что происходит более подробно:

  1. выделяется память для Derived ;
  2. вызывается конструктор Derived(double, int) , где cost = 1.3, а id = 5;
  3. компилятор проверяет, запрашивали ли мы конкретный конструктор для класса Base . Так и есть! Поэтому он вызывает Base(int) с id = 5;
  4. список инициализации конструктора класса Base устанавливает m_id равным 5;
  5. выполняется тело конструктора класса Base , которое ничего не делает;
  6. конструктор класса Base возвращает выполнение;
  7. список инициализации конструктора класса Derived устанавливает m_cost равным 1,3;
  8. выполняется тело конструктора класса Derived , которое ничего не делает;
  9. конструктор класса Derived возвращает выполнение.

Это может показаться несколько сложным, но на самом деле всё очень просто. Всё, что происходит, – это то, что конструктор Derived вызывает конкретный конструктор Base для инициализации части Base объекта. Поскольку m_id находится в части Base объекта, конструктор Base является единственным конструктором, который может инициализировать это значение.

Обратите внимание, что не имеет значения, где в списке инициализации конструктора Derived вызывается конструктор Base – он всегда будет выполняться первым.

Теперь мы можем сделать наши члены закрытыми

Теперь, когда вы знаете, как инициализировать члены базового класса, нет необходимости держать наши переменные-члены открытыми. Мы снова делаем наши переменные-члены закрытыми, как и должно быть.

Напоминаем, что к открытым членам может получить доступ кто угодно. Доступ к закрытым членам могут получить только функции-члены того же класса. Обратите внимание, что это означает, что производные классы не могут напрямую обращаться к закрытым членам базового класса! Для доступа к закрытым членам базового класса производные классы должны будут использовать функции доступа.

Рассмотрим следующий код:

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

Этот код печатает следующее, как и ожидалось:

Подробнее о спецификаторах доступа мы поговорим в следующем уроке.

Еще один пример

Давайте посмотрим на еще одну пару классов, с которыми мы ранее работали:

Как мы уже писали ранее, BaseballPlayer инициализирует только свои собственные члены и не указывает, какой конструктор Person использовать. Это означает, что каждый созданный нами BaseballPlayer будет использовать конструктор Person по умолчанию, который инициализирует имя пустой строкой и возраст значением 0. Поскольку имеет смысл дать нашему BaseballPlayer имя и возраст при его создании, мы должны изменить его конструктор, чтобы добавить эти параметры.

Вот наши обновленные классы, которые используют закрытые члены, причем класс BaseballPlayer вызывает соответствующий конструктор Person для инициализации унаследованных переменных-членов Person :

Теперь мы можем создавать бейсболистов так:

Этот код выводит:

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

Цепочки наследования

Классы в цепочке наследования работают точно так же.

В этом примере класс C является производным от класса B , который является производным от класса A . Итак, что происходит, когда мы создаем экземпляр объекта класса C ?

Сначала main() вызывает C(int, double, char) . Конструктор C вызывает B(int, double) . Конструктор B вызывает A(int) . Поскольку A ни от кого не наследуется, это первый класс, который мы создадим. A создается, печатает значение 5 и возвращает управление B . B создается, печатает значение 4.3 и возвращает управление C . C создается, печатает значение ' R ' и возвращает управление main() . Готово!

Таким образом, эта программа печатает:

Стоит отметить, что конструкторы могут вызывать конструкторы только их непосредственного родительского/базового класса. Следовательно, конструктор C не может напрямую вызывать или передавать параметры конструктору A . Конструктор C может вызывать только конструктор B (который отвечает за вызов конструктора A ).

Деструкторы

Когда производный класс уничтожается, каждый деструктор вызывается в порядке, обратном созданию. В приведенном выше примере, когда c уничтожается, сначала вызывается деструктор C , затем деструктор B , а затем деструктор A .

Резюме

При создании производного класса конструктор производного класса отвечает за определение того, какой вызывается конструктор базового класса. Если конструктор базового класса не указан, будет использоваться конструктор базового класса по умолчанию. В этом случае, если конструктор базового класса по умолчанию не может быть найден (или создан по умолчанию), компилятор выдаст ошибку. Далее классы создаются в порядке от самого базового к самому производному.

На этом этапе вы достаточно понимаете наследование в C++, чтобы создавать свои собственные наследованные классы!

Небольшой тест

Вопрос 1

Давайте реализуем наш пример с фруктами, о котором мы говорили во введении в наследование. Создайте базовый класс Fruit , содержащий два закрытых члена: имя, name , ( std::string ) и цвет, color , ( std::string ). Создайте класс для яблока, Apple , наследованный от Fruit . У Apple должен быть дополнительный закрытый член: клетчатка, fiber , ( double ). Создайте класс для банана, Banana , который также наследуется от Fruit . У Banana нет дополнительных членов.

Должна запуститься следующая программа:

Она должна напечатать следующее:

Подсказка: поскольку a и b являются константами, вам нужно помнить о константности. Убедитесь, что ваши параметры и функции имеют значение const .

Assembly line — An assembly line is a manufacturing process in which parts (usually interchangeable parts) are added to a product in a sequential manner using optimally planned logistics to create a finished product much faster than with handcrafting type… … Wikipedia

Assembly language — See the terminology section below for information regarding inconsistent use of the terms assembly and assembler. Motorola MC6800 Assembly Language An assembly language is a low level programming language for computers, microprocessors,… … Wikipedia

Tool and die maker — Not to be confused with tap and die. Tool and die makers are workers in the manufacturing industry who make jigs, fixtures, dies, molds, machine tools, cutting tools (such as milling cutters and form tools), gauges, and other tools used in… … Wikipedia

tool and die making — Industrial art of manufacturing stamping dies, plastics molds, and jigs and fixtures to be used in the mass production of solid objects. The making of dies for punch presses constitutes most of the work done in tool and die shops, and most such… … Universalium

assembly line — an arrangement of machines, tools, and workers in which a product is assembled by having each perform a specific, successive operation on an incomplete unit as it passes by in a series of stages organized in a direct line. Also called production… … Universalium

High Level Assembly — Infobox Software name = High Level Assembly (HLA) Language developer = Randall Hyde latest release version = 1.102 Beta latest release date = release date|2008|05|02 operating system = Windows, Linux, FreeBSD, Mac OS X genre = Assembler license … Wikipedia

machine tool — machine tooled, adj. a power operated machine, as a lathe, used for general cutting and shaping of metal and other substances. [1860 65] * * * Stationary, power driven machine used to cut, shape, or form materials such as metal and wood. Machine… … Universalium

Cytochrome c assembly protein family — Cytochrome C assembly protein Identifiers Symbol Cytochrom C asm Pfam PF01578 Pfam clan C … Wikipedia

Molecular self-assembly — An example of a molecular self assembly through hydrogen bonds.[1] Molecular self assembly is the process by which molecules adopt a defined arrangement without guidance or management from an outside source. Th … Wikipedia

ACP-EU Joint Parliamentary Assembly — The ACP EU Joint Parliamentary Assembly was created to bring together the elected representatives of the European Community (the Members of the European Parliament) and the elected representatives of the African, Caribbean and Pacific states (… … Wikipedia

bowl protector running and retrieving tool — инструмент для спуска и подъёма защитной втулки (устанавливаемой в устьевую головку с целью предохранения рабочих поверхностей головки от повреждения при прохождении бурового инструмента)

cam actuated running tool — инструмент для спуска с гребёнками; спусковой инструмент с гребенчатыми плашками

casing hanger packoff retrieving and reinstallation tool — инструмент для съёма и повторной установки уплотнения подвесной головки обсадной колонны

casing hanger running tool — инструмент для спуска подвесной головки обсадной колонны (для спуска обсадной колонны и подвески её в подвесной головке предыдущей колонны)

direct drive casing hanger running tool — специальный инструмент для одновременного спуска обсадной колонны и уплотнительного узла её подвесной головки

drill pipe emergency hangoff tool — инструмент для аварийной подвески бурильной колонны (на плашках одного из превенторов подводного блока превенторов)

guideline connector installing tool — инструмент для установки соединителя направляющего каната (подводного устьевого оборудования)

pump open circulating tool — инструмент для циркуляции, открываемый давлением

remote guide line connector releasing tool — инструмент для отсоединения дистанционно управляемого замка направляющего каната

rotation release running tool — инструмент для спуска, отсоединяющийся вращением

seal assembly retrieving tool — инструмент для извлечения уплотнительного устройства (в случае его неисправности)

seal assembly running tool — инструмент для спуска уплотнительного узла (для уплотнения подвесной головки обсадной колонны)

temporary abandonment cup running and retrieving tool — инструмент для спуска и извлечения колпака временно оставляемой морской скважины

wireline operated circulation tool — управляемый тросом инструмент для циркуляции (используемый при пробной эксплуатации скважины)

cam actuated running tool — инструмент для спуска с гребенками; спусковой инструмент с гребенчатыми плашками

casing hanger packoff retrieving and reinstallation tool — инструмент для съёма и повторной установки уплотнения подвесной головки обсадной колонны

circulating through-flowline well servicing tool — инструмент, закачиваемый циркуляцией через выкидную линию

direct drive casing hanger running tool — специальный инструмент для одновременного спуска обсадной колонны и уплотнительного узла её подвесной головки

drill pipe emergency hangoff tool — инструмент для аварийной подвески бурильной колонны (на плашках одного из противовыбросовых превенторов подводного блока)

seal assembly running tool — инструмент для спуска уплотнительного узла (для уплотнения подвесной головки обсадной колонны)

to orient a deflecting tool — ориентировать отклоняющий инструмент;

to run tools to the bottom — спускать инструмент на забой скважины;

wireline operated circulation tool — управляемый тросом инструмент для циркуляции (используемый при пробной эксплуатации скважины)

tool closed — скважинный инструмент закрыт;

to feed drilling tool — подавать буровой инструмент;

to orient a deflecting tool — ориентировать отклоняющий инструмент;

to run tools to the bottom — спускать инструмент на забой скважины;

to set a deflecting tool on bottom of hole — устанавливать отклоняющий инструмент на забой ствола скважины;

to stack the tools — выбрасывать бурильные трубы на мостки;

tool to tubing substitute — переводник с инструмента на насосно-компрессорные трубы;

19 assembly

ball joint extension assembly — удлинитель шарового соединения; переводник шарового соединения (водоотделяющей колонны)

single trip hanger assembly — однорейсовый узел подвесной головки (спускаемый и устанавливаемый в подводном устье за один рейс)

wire line guide assembly — устройство для укладки каната, канатоукладчик

ball joint extension assembly — удлинитель шарового соединения; переводник шарового соединения (водоотделяющей колонны)

marine riser torque-down seal assembly — уплотнительный узел водоотделяющей колонны, срабатывающий под действием вращающего момента

single trip hanger assembly — однорейсовый узел подвесной головки (спускаемый и устанавливаемый в под водном устье за один рейс)

unsealed drilling bit bearing assembly — открытая опора бурового долота,

20 tool

См. также в других словарях:

tool|ing — «TOO lihng», noun. 1. work done with a tool. 2. ornamentation made with a tool: »leather tooling. 3. the assembly of machine tools in a factory … Useful english dictionary

Assembly line — An assembly line is a manufacturing process in which parts (usually interchangeable parts) are added to a product in a sequential manner using optimally planned logistics to create a finished product much faster than with handcrafting type… … Wikipedia

Assembly language — See the terminology section below for information regarding inconsistent use of the terms assembly and assembler. Motorola MC6800 Assembly Language An assembly language is a low level programming language for computers, microprocessors,… … Wikipedia

Tool and die maker — Not to be confused with tap and die. Tool and die makers are workers in the manufacturing industry who make jigs, fixtures, dies, molds, machine tools, cutting tools (such as milling cutters and form tools), gauges, and other tools used in… … Wikipedia

tool and die making — Industrial art of manufacturing stamping dies, plastics molds, and jigs and fixtures to be used in the mass production of solid objects. The making of dies for punch presses constitutes most of the work done in tool and die shops, and most such… … Universalium

assembly line — an arrangement of machines, tools, and workers in which a product is assembled by having each perform a specific, successive operation on an incomplete unit as it passes by in a series of stages organized in a direct line. Also called production… … Universalium

High Level Assembly — Infobox Software name = High Level Assembly (HLA) Language developer = Randall Hyde latest release version = 1.102 Beta latest release date = release date|2008|05|02 operating system = Windows, Linux, FreeBSD, Mac OS X genre = Assembler license … Wikipedia

machine tool — machine tooled, adj. a power operated machine, as a lathe, used for general cutting and shaping of metal and other substances. [1860 65] * * * Stationary, power driven machine used to cut, shape, or form materials such as metal and wood. Machine… … Universalium

Cytochrome c assembly protein family — Cytochrome C assembly protein Identifiers Symbol Cytochrom C asm Pfam PF01578 Pfam clan C … Wikipedia

Molecular self-assembly — An example of a molecular self assembly through hydrogen bonds.[1] Molecular self assembly is the process by which molecules adopt a defined arrangement without guidance or management from an outside source. Th … Wikipedia

ACP-EU Joint Parliamentary Assembly — The ACP EU Joint Parliamentary Assembly was created to bring together the elected representatives of the European Community (the Members of the European Parliament) and the elected representatives of the African, Caribbean and Pacific states (… … Wikipedia

Когда вы создаете экземпляр нового объекта, конструктор объекта вызывается неявно. Нередко у классов бывает несколько конструкторов, которые содержат перекрывающуюся функциональность. Рассмотрим следующий класс:

Этот класс имеет два конструктора: конструктор по умолчанию и конструктор, принимающий целочисленное значение. Поскольку часть конструктора «код для выполнения A» требуется обоим конструкторам, этот код дублируется в каждом конструкторе.

Как вы (надеюсь) уже поняли, дублирования кода следует максимально избегать. Поэтому давайте рассмотрим несколько способов решения этой проблемы.

Очевидное решение не работает

Очевидным решением было бы, чтобы конструктор Foo(int) вызывал конструктор Foo() для выполнения части A.

Однако, если вы попытаетесь заставить один конструктор вызвать другой конструктор таким образом, код скомпилируется и, возможно, вызовет предупреждение, но он не будет работать так, как вы ожидаете. И вы, вероятно, даже с отладчиком потратите много времени, пытаясь выяснить, почему. Что происходит на самом деле: Foo(); создает новый объект Foo , который немедленно отбрасывается, поскольку он не хранится в переменной.

Делегирующие конструкторы

Конструкторам разрешено вызывать другие конструкторы. Это называется делегирующими конструкторами (или цепочкой конструкторов).

Чтобы один конструктор вызывал другой, просто вызовите конструктор в списке инициализаторов членов. Это тот случай, когда прямой вызов другого конструктора приемлем. Применительно к нашему примеру выше:

Это работает именно так, как вы ожидали. Убедитесь, что вы вызываете конструктор из списка инициализаторов членов, а не в теле конструктора.

Вот еще один пример использования делегирующих конструкторов для уменьшения избыточности кода:

Этот класс имеет 2 конструктора, один из которых делегирует конструктору Employee(int, const std::string&) . Таким образом, количество избыточного кода сводится к минимуму (нам нужно написать только одно тело конструктора вместо двух).

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

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

Лучшая практика

Если у вас есть несколько конструкторов с одинаковой функциональностью, используйте делегирующие конструкторы, чтобы избежать дублирования кода.

Использование отдельной функции

Соответственно, вы можете оказаться в ситуации, когда захотите написать функцию-член, чтобы повторно инициализировать класс до значений по умолчанию. Поскольку у вас, вероятно, уже есть конструктор, который делает это, у вас может возникнуть соблазн попытаться вызвать этот конструктор из вашей функции-члена. Однако попытка вызвать конструктор напрямую обычно приводит к неожиданному поведению, как мы показали выше. Многие разработчики просто копируют код из конструктора в функцию инициализации, что работает, но приводит к дублированию кода. Лучшее решение в этом случае – переместить код из конструктора в новую функцию и заставить конструктор вызывать эту функцию для выполнения работы по «инициализации» данных:

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

Мы говорим «инициализировать», но это не настоящая инициализация. К тому времени, когда конструктор вызывает init() , члены уже существуют и были инициализированы значениями по умолчанию или не инициализированы. Функция init может только присваивать значения членам. Существуют типы, экземпляры которых невозможно создать без аргументов, потому что у них нет конструктора по умолчанию. Если какой-либо из членов класса принадлежит такому типу, функция init не сработает, и конструкторы должны сами инициализировать эти члены.

Довольно часто добавляют функцию init() , которая инициализирует переменные-члены их значениями по умолчанию, а затем каждый конструктор вызывает эту функцию init() перед выполнением задач, зависящих от параметров. Это сводит к минимуму дублирование кода и позволяет явно вызывать init() из любого места.

Одно небольшое предостережение: будьте осторожны при использовании функций init() и динамически выделяемой памяти. Поскольку функции init() могут быть вызваны кем угодно в любое время, динамически выделяемая память может быть уже выделена, а может и не быть выделена при вызове init() . Будьте осторожны, чтобы обработать эту ситуацию правильно – это может немного сбивать с толку, поскольку ненулевой указатель может либо указывать на динамически выделенную память, либо быть неинициализированным указателем!

Когда все члены класса (или структуры) являются открытыми, для инициализации этого класса (или структуры) мы можем использовать агрегатную инициализацию напрямую, используя инициализацию списком:

Однако, как только мы сделаем какие-либо переменные-члены закрытыми, мы больше не сможем инициализировать классы таким образом. И это понятно: если у вас нет прямого доступа к переменной (поскольку она является закрытой), у вас не должно быть возможности напрямую инициализировать ее.

Итак, как же инициализировать класс с закрытыми переменными-членами? Ответ: через конструкторы.

Конструкторы

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

В отличие от обычных функций-членов, у конструкторов есть определенные правила того, как они должны называться:

  1. конструкторы должны иметь то же имя, что и класс (с такими же заглавными буквами);
  2. конструкторы не имеют возвращаемого типа (даже не void ).

Конструкторы по умолчанию

Конструктор, который не принимает параметров (или все параметры имеют значения по умолчанию), называется конструктором по умолчанию. Конструктор по умолчанию вызывается, если не предоставлены значения инициализации, предоставляемые пользователем.

Вот пример класса, у которого есть конструктор по умолчанию:

Этот класс был разработан для хранения дробного значения в виде целочисленных числителя и знаменателя. Мы определили конструктор по умолчанию с именем Fraction (такое же, как у класса).

Поскольку мы создаем экземпляр объекта типа Fraction без аргументов, то сразу после выделения памяти для объекта будет вызван конструктор по умолчанию, и наш объект будет инициализирован.

Эта программа дает следующий результат:

Обратите внимание, что числитель и знаменатель были инициализированы значениями, которые мы установили в конструкторе по умолчанию! Без конструктора по умолчанию числитель и знаменатель будут иметь мусорные значения, пока мы явно не присвоим им осмысленные значения или не инициализируем их другими способами (помните: переменные базовых типов не инициализируются значениями по умолчанию).

Прямая и унифицированная инициализации с использованием конструкторов с параметрами

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

Обратите внимание, что теперь у нас есть два конструктора: конструктор по умолчанию, который будет вызываться в случае по умолчанию, и второй конструктор, который принимает два параметра. Благодаря перегрузке функций эти два конструктора могут мирно сосуществовать в одном классе. Фактически, вы можете определить столько конструкторов, сколько захотите, при условии, что каждый имеет уникальную сигнатуру (количество и типы параметров).

Итак, как нам использовать этот конструктор с параметрами? Это просто! Мы можем использовать инициализацию списком или прямую инициализацию:

Как всегда, мы предпочитаем инициализацию списком. Причины для использования прямой инициализации при вызове конструкторов (шаблоны и std::initializer_list ) мы узнаем позже в этой серии статей. Существует еще один специальный конструктор, который может заставить инициализацию с фигурными скобками делать что-то другое, в этом случае мы должны использовать прямую инициализацию. Об этих конструкторах мы поговорим позже.

Обратите внимание, что мы дали второму параметру конструктора с параметрами значение по умолчанию, поэтому следующее также допустимо:

Значения по умолчанию для конструкторов работают точно так же, как и с любыми другими функциями, поэтому в приведенном выше случае, когда мы вызываем six , функция Fraction(int, int) вызывается со вторым параметром, по умолчанию равным 1.

Правило

Для инициализации объектов класса используйте инициализацию с фигурными скобками.

Копирующая инициализация с использованием оператора присваивания при работе с классами

Как и в случае с переменными базовых типов, инициализировать классы также можно, используя копирующую инициализацию:

Однако при работе с классами мы рекомендуем избегать этой формы инициализации, поскольку она может быть менее эффективной. Хотя прямая инициализация, унифицированная инициализация и копирующая инициализация работают одинаково с базовыми типами, копирующая инициализация с классами работают не одинаково (хотя конечный результат часто бывает одинаковым). Мы рассмотрим различия более подробно в следующей главе.

Уменьшение количества конструкторов

В приведенном выше объявлении двух конструкторов класса Fraction конструктор по умолчанию на самом деле несколько избыточен. Мы могли бы упростить этот класс следующим образом:

Хотя этот конструктор по-прежнему является конструктором по умолчанию, теперь он определен таким образом, что может принимать одно или два значения, предоставленных пользователем.

При реализации конструкторов подумайте, как вы можете уменьшить их количество за счет разумной установки значений по умолчанию.

Напоминание о параметрах по умолчанию

Правила определения и вызова функций с параметрами по умолчанию (описанные в уроке «8.12 – Аргументы по умолчанию») применимы и к конструкторам. Напомним, что при определении функции с параметрами по умолчанию все параметры по умолчанию должны следовать после любых параметров, отличных от параметров по умолчанию, т.е. после параметра по умолчанию не может быть параметров, не заданных по умолчанию.

Это может привести к неожиданным результатам для классов, которые имеют несколько параметров по умолчанию разных типов. Рассмотрим следующий код:

В s4 мы попытались создать Something , предоставив только double . Это не будет компилироваться, поскольку правила соответствия аргументов параметрам по умолчанию не позволят нам пропустить не крайний правый параметр (в данном случае крайний левый параметр типа int ).

Если мы хотим иметь возможность создать Something только c double , нам нужно добавить второй (не используемый по умолчанию) конструктор:

Неявно созданный конструктор по умолчанию

Если в вашем классе нет конструкторов, C++ автоматически сгенерирует для вас открытый конструктор по умолчанию. Иногда это называют неявным конструктором («implicit constructor», или неявно сгенерированным конструктором).

Рассмотрим следующий класс:

У этого класса нет конструктора. Следовательно, компилятор сгенерирует конструктор, который позволит нам создать объект Date без аргументов.

Этот конкретный неявный конструктор позволяет нам создать объект Date без аргументов, но не инициализирует ни один из его членов, если мы не создадим объект Date с помощью прямой инициализации или инициализации списком (поскольку все члены принадлежат базовым типам, а те при создании не инициализируется). Если бы у Date были члены, которые сами принадлежат типам классов, например std::string , конструкторы этих членов вызывались бы автоматически.

Чтобы обеспечить инициализацию переменных-членов, мы можем инициализировать их при их объявлении.

Хотя вы не видите неявно созданный конструктор, вы можете доказать, что он существует:

Приведенный выше код компилируется, потому что объект Date будет использовать неявный конструктор (который является открытым).

Если в вашем классе есть какие-либо другие конструкторы, неявно сгенерированный конструктор предоставлен не будет. Например:

Чтобы разрешить создание Date без аргументов, добавьте в конструктор аргументы по умолчанию, добавьте пустой конструктор по умолчанию или явно добавьте конструктор по умолчанию:

Использование = default – это почти то же самое, что добавление конструктора по умолчанию с пустым телом. Единственное отличие состоит в том, что = default позволяет нам безопасно инициализировать переменные-члены, даже если у них нет инициализатора:

Использование = default длиннее, чем написание конструктора с пустым телом, но лучше выражает ваши намерения (создать конструктор по умолчанию) и безопаснее. = default также работает для других специальных конструкторов, о которых мы поговорим в будущем.

Правило

Если у вас в вашем классе есть конструкторы, и вам нужен конструктор по умолчанию, который ничего не делает, используйте = default .

Классы, содержащие классы

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

Это можно продемонстрировать следующим образом:

Этот код печатает:

Когда создается переменная b , вызывается конструктор B() . Перед выполнением тела конструктора инициализируется m_a , вызывая конструктор по умолчанию класса A . Это печатает " А ". Затем управление возвращается конструктору B , и выполняется тело конструктора B .

Это имеет смысл, если подумать, что конструктор B() может захотеть использовать переменную m_a , поэтому сначала лучше инициализировать m_a !

Отличие от последнего примера в предыдущем разделе в том, что m_a принадлежит типу класса. Члены типа класса инициализируются, даже если мы не инициализируем их явно.

В следующем уроке мы поговорим о том, как инициализировать эти переменные-члены класса.

Замечания о конструкторах

Многие начинающие программисты не понимают, создают ли конструкторы объекты или нет. Они этого не делают – компилятор выполняет выделение памяти для объекта до вызова конструктора.

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

Во-вторых, конструкторы можно использовать для инициализации объектов. Вопрос о том, действительно ли конструктор выполняет инициализацию, зависит от программиста. Синтаксически допустимо иметь конструктор, который вообще не выполняет инициализацию (конструктор по-прежнему служит цели создания объекта, как указано выше).

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

Лучшая практика

Всегда инициализируйте все переменные-члены в ваших объектах.

Наконец, конструкторы предназначены для использования для инициализации только при создании объекта. Не следует пытаться вызвать конструктор для повторной инициализации существующего объекта. Хотя это может компилироваться, результаты будут не такими, как вы планировали (вместо этого компилятор создаст временный объект, а затем отбросит его).

Небольшой тест

Вопрос 1

Напишите класс мяча с именем Ball . Ball должен иметь две закрытые переменные-члены со значениями по умолчанию: m_color (" black ") и m_radius (10.0). Ball должен предоставить конструкторы для установки только m_color , установки только m_radius , установки обоих или ни одного из значений. В этом вопросе теста не используйте параметры по умолчанию для ваших конструкторов. Также напишите функцию для печати цвета и радиуса мяча.

Следующая программа-пример должна скомпилироваться:

и выдавать следующий результат:

b) Обновите свой ответ на предыдущий вопрос, чтобы использовать конструкторы с параметрами по умолчанию. Используйте как можно меньше конструкторов.

Вопрос 2

Что произойдет, если вы не объявите конструктор по умолчанию?

Если вы не определили никаких других конструкторов, компилятор создаст для вас пустой открытый конструктор по умолчанию. Это означает, что ваши объекты будут создаваться без параметров.

Если вы определили другие конструкторы (по умолчанию или нет), компилятор не создаст для вас конструктор по умолчанию. Предполагая, что вы сами не предоставили конструктор по умолчанию, ваши объекты не будут создаваться без аргументов.

Читайте также: