- BestProg
- Конструкторы. Конструкторы по умолчанию. Вызов конструкторов класса из других конструкторов
- Содержание
- Вызываем конструктор базового типа в произвольном месте
- Подготовка
- Удаление дублирующегося кода
- Получение доступа к конструктору базового типа
- Вызов конструктора базового типа в произвольном месте
- Изменение порядка вызова конструкторов базового типа
- Осмысление результата
- Конструкторы (C++)
- Списки инициализаторов членов
- Конструкторы по умолчанию
- Конструкторы копий
- Конструкторы перемещения
- Явно заданные по умолчанию и удаленные конструкторы
- конструкторы constexpr
- Конструкторы списка инициализаторов
- Явные конструкторы
- Порядок создания
- Производные конструкторы и расширенная агрегатная инициализация
- Конструкторы для классов с несколькими наследованиями
- Делегирование конструкторов
- Наследование конструкторов (C++ 11)
- Конструкторы и составные классы
BestProg
Конструкторы. Конструкторы по умолчанию. Вызов конструкторов класса из других конструкторов
Содержание
Поиск на других ресурсах:
1. Понятие конструктора по умолчанию
Конструктор по умолчанию (default constructor) – это конструктор, который не имеет параметров. Конструктор по умолчанию может объявляться в классе явным образом или генерироваться автоматически.
В наиболее общем случае, для класса ClassName , конструктор по умолчанию имеет следующий вид:
2. В каких случаях конструктор по умолчанию генерируется в классе автоматически а в каких нет? Пример
Если в классе не объявить ни одного конструктора, то, будет генерироваться конструктор по умолчанию. То есть, конструктор по умолчанию генерируется в классе автоматически только в том случае, если класс не содержит реализации других конструкторов. Если класс содержит реализацию хотя бы одного конструктора с параметрами, то, чтобы объявить конструктор по умолчанию, его нужно объявлять в классе явным образом.
Например. В следующем объявлении класса конструктор по умолчанию генерируется автоматически
Вышеприведенный код означает, что можно объявлять объект класса с использованием конструктора по умолчанию:
Если в тело класса CMyClass добавить хотя бы один другой конструктор (например, конструктор с одним параметром), то конструктор по умолчанию автоматически генерироваться не будет
После вышеприведенной реализации объявить объект с использованием конструктора по умолчанию не удастся. Однако, можно объявить объект с использованием конструктора с одним параметром
В результате выполнения вышеприведенной строки будет выдана ошибка компиляции:
The constructor CMyClass() is undefined
Для того, чтобы иметь реализацию конструктора по умолчанию и объявлять объект класса с использованием конструктора по умолчанию, его нужно задавать явно. Это может быть, например, следующим образом
После такой реализации можно создавать экземпляр класса с использованием двух конструкторов, например
3. Вызов конструкторов из других конструкторов. Пример
Язык программирования Java позволяет осуществлять вызов конструкторов класса из другого конструктора этого же класса. Для этого используется ключевое слово this , которое есть ссылкой на текущий класс.
Пример. В примере демонстрируется использование класса CPixel , который реализует пиксел на экране монитора.
Использование класса CPixel в другом программном коде (методе)
4. Какие ограничения (требования) накладываются на вызов других конструкторов из конструктора класса?
Чтобы корректно вызвать другие конструкторы из конструктора класса, нужно придерживаться следующих требований (ограничений):
- вызвать можно только один другой конструктор класса. Вызвать два и больше других конструктора этого класса запрещено. Это вытекает из логики, что конструктор класса предназначен для создания объекта класса только один раз (а не два и больше раза);
- вызов другого конструктора должен быть первой операцией в вызывающем конструкторе. Если в вызывающем конструкторе вызов другого конструктора реализовать второй (третьей и т.д.) операцией, то компилятор выдаст ошибку.
5. Можно ли вызвать конструктор из обычного метода класса?
Нет, нельзя. Конструктор класса может вызваться только из другого конструктора этого же класса. Главное назначение конструктора класса – создание экземпляров (объектов) класса. Этих привилегий лишены методы класса.
Источник
Вызываем конструктор базового типа в произвольном месте
Недавно проходил собеседование, и среди прочих был вопрос о порядке вызова конструкторов в C#. После ответа собеседующий решил продемонстрировать эрудицию и заявил, что вот в Java конструктор базового типа можно вызвать в любом месте конструктора производного типа, и C#, конечно, в этом проигрывает.
Но это уже не имело значения, потому что вызов был принят.
Подготовка
Создаем цепочку наследования. Для простоты будем использовать конструкторы без параметров. В конструкторе будем выводить информацию о типе и идентификатор объекта, на котором он вызывается.
И получаем вывод:
Type ‘A’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Перед выполнением конструктор может вызвать либо другой конструктор того же типа, либо любой доступный конструктор базового типа. Если вызов не указан явно, компилятор подставит вызов конструктора базового типа без параметров. Если базовый тип не предоставляет такой конструктор, происходит ошибка компиляции. При этом конструктор не может явно вызвать сам себя:
и таким фокусом компилятор тоже не провести:
Удаление дублирующегося кода
Добавляем вспомогательный класс:
И заменяем во всех конструкторах
Однако теперь программа выводит:
Type ‘C’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
В нашем случае можно использовать следующую хитрость. Кто знает о типах времени компиляции? Компилятор. А еще он выбирает перегрузки методов на основе этих типов. И для обобщенных типов и методов генерирует сконструированные сущности тоже он. Поэтому возвращаем правильный вывод типов, переписав метод Trace следующим образом:
Получение доступа к конструктору базового типа
Здесь на помощь приходит рефлексия. Добавляем в Extensions метод:
Вызов конструктора базового типа в произвольном месте
Меняем содержимое конструкторов B и C на:
Теперь вывод выглядит так:
Type ‘A’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Изменение порядка вызова конструкторов базового типа
Внутри типа A создаем вспомогательный тип:
Так как здесь важна только семантика, конструктор типа целесообразно сделать закрытым. Создание экземпляров не имеет смысла. Тип предназначен исключительно для различения перегрузок конструкторов типа A и производных от него. По этой же причине тип следует разместить внутри A и сделать защищенным.
Добавляем в A, B и C соответствующие конструкторы:
Для типов B и C ко всем конструкторам добавляем вызов:
И вывод становится:
Type ‘C’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Осмысление результата
Добавив в Extensions метод:
и вызвав его во всех конструкторах, принимающих CtorHelper, мы получим вывод:
Type ‘A’ surrogate .ctor called on object #58225482
Type ‘B’ surrogate .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Type ‘A’ surrogate .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Порядок следования конструкторов по принципу базовый/производный, конечно, не изменился. Но все же порядок следования доступных клиентскому коду конструкторов, несущих смысловую нагрузку, удалось поменять благодаря перенаправлению через вызовы недоступных клиенту ничего не делающих вспомогательных конструкторов.
Источник
Конструкторы (C++)
Чтобы настроить способ инициализации элементов класса или вызвать функции при создании объекта класса, определите конструктор. Конструкторы имеют имена, совпадающие с именами классов, и не имеют возвращаемых значений. Можно определить столько перегруженных конструкторов, сколько необходимо для настройки инициализации различными способами. Как правило, конструкторы имеют открытый доступ, поэтому код за пределами определения класса или иерархии наследования может создавать объекты класса. Но можно также объявить конструктор как protected или private .
При необходимости конструкторы могут получить список инициализации элемента. Это более эффективный способ инициализации членов класса, чем назначение значений в теле конструктора. В следующем примере показан класс Box с тремя перегруженными конструкторами. Последние два списка инициализации элементов use:
При объявлении экземпляра класса компилятор выбирает конструктор для вызова на основе правил разрешения перегрузки:
- Конструкторы могут быть объявлены как inline , явныеfriend или constexpr.
- Конструктор может инициализировать объект, объявленный как const , volatile или const volatile . Объект станет const после завершения конструктора.
- Чтобы определить конструктор в файле реализации, присвойте ему полное имя с любой другой функцией-членом: Box::Box() <. >.
Списки инициализаторов членов
Конструктор может дополнительно иметь список инициализаторов членов, который инициализирует члены класса перед выполнением тела конструктора. (Обратите внимание, что список инициализаторов членов не то же самое, что и Список инициализаторов типа std :: initializer_list.)
Использование списка инициализаторов членов предпочтительнее, чем назначение значений в теле конструктора, так как он непосредственно Инициализирует элемент. В следующем примере показан список инициализаторов членов, состоящий из всех выражений идентификаторов (аргументов) после двоеточия:
Идентификатор должен ссылаться на член класса; он инициализируется значением аргумента. Аргумент может быть одним из параметров конструктора, вызовом функции или std:: initializer_list .
const члены и члены ссылочного типа должны быть инициализированы в списке инициализаторов членов.
В списке инициализаторов должны быть сделаны вызовы для параметризованных конструкторов базового класса, чтобы гарантировать, что базовый класс полностью инициализирован до выполнения производного конструктора.
Конструкторы по умолчанию
Конструкторы по умолчанию обычно не имеют параметров, но могут иметь параметры со значениями по умолчанию.
Конструкторы по умолчанию являются одной из специальных функций элементов. Если в классе не объявлен ни один конструктор, компилятор предоставляет неявный inline конструктор по умолчанию.
Если вы полагаетесь на неявный конструктор по умолчанию, обязательно инициализируйте элементы в определении класса, как показано в предыдущем примере. Без этих инициализаторов члены будут неинициализированы, а вызов Volume () создаст значение мусора. Как правило, рекомендуется инициализировать элементы таким образом, даже если не полагается на неявный конструктор по умолчанию.
Можно запретить компилятору создавать неявный конструктор по умолчанию, определив его как Удаленный:
Созданный компилятором конструктор по умолчанию будет определен как удаленный, если какие-либо члены класса не являются конструируемым по умолчанию. Например, все члены типа класса и их члены типа класса должны иметь доступ к конструктору по умолчанию и деструкторам, которые доступны. Все элементы данных ссылочного типа, а также const члены класса должны иметь инициализатор членов по умолчанию.
При вызове конструктора, созданного компилятором по умолчанию, и попытке использовать круглые скобки выдается предупреждение:
Это пример проблемы Most Vexing Parse (наиболее неоднозначного анализа). Поскольку выражение примера можно интерпретировать как объявление функции или как вызов конструктора по умолчанию и в связи с тем, что средства синтаксического анализа C++ отдают предпочтение объявлениям перед другими действиями, данное выражение обрабатывается как объявление функции. Дополнительные сведения см. в разделе досадной Parse.
В случае явного объявления конструкторов компилятор не предоставляет конструктор по умолчанию:
Если у класса нет конструктора по умолчанию, массив объектов этого класса не может быть создан только с помощью синтаксиса двух квадратных скобок. Например, в представленном выше блоке кода массив Boxes не может быть объявлен следующим образом:
Однако можно использовать набор списков инициализаторов для инициализации массива объектов Box:
Дополнительные сведения см. в разделе инициализаторы.
Конструкторы копий
Конструктор копии Инициализирует объект, копируя значения элементов из объекта того же типа. Если все члены класса являются простыми типами, такими как скалярные значения, конструктор копий, созданный компилятором, достаточно, и вам не нужно определять собственный. Если для класса требуется более сложная инициализация, необходимо реализовать пользовательский конструктор копии. Например, если член класса является указателем, необходимо определить конструктор копии, чтобы выделить новую память и скопировать значения из объекта, указывающего на другой объект. Созданный компилятором конструктор копий просто копирует указатель, так что новый указатель по-прежнему указывает на расположение в памяти другого.
Конструктор копии может иметь одну из следующих сигнатур:
При определении конструктора копии необходимо также определить оператор присваивания копирования (=). Дополнительные сведения см. в разделе конструкторы присваивания и копирования и операторы присваивания копирования.
Вы можете запретить копирование объекта, определив конструктор копии как удаленный:
Попытка копирования объекта приводит к ошибке C2280: попытка ссылки на удаленную функцию.
Конструкторы перемещения
Конструктор перемещения — это специальная функция-член, которая перемещает владение данными существующего объекта в новую переменную без копирования исходных данных. Он принимает в качестве первого параметра ссылку rvalue, а все дополнительные параметры должны иметь значения по умолчанию. Конструкторы перемещения могут значительно повысить эффективность программы при передаче больших объектов.
Компилятор выбирает конструктор перемещения в определенных ситуациях, где объект инициализируется другим объектом того же типа, который будет уничтожен и больше не нуждается в его ресурсах. В следующем примере показан один случай, когда конструктор перемещения выбирается с помощью разрешения перегрузки. В конструкторе, который вызывает get_Box() , возвращаемое значение является xValue (значение срока действия). Она не назначается какой-либо переменной, поэтому она собирается выйти из области. Чтобы создать мотивацию для этого примера, пусть Box имеет большой вектор строк, который представляет его содержимое. Вместо копирования вектора и его строк конструктор Move «украсть» из поля «Box Expires», чтобы вектор теперь принадлежал новому объекту. Вызов std::move является необходимым, поскольку оба vector string класса и реализуют собственные конструкторы перемещения.
Если класс не определяет конструктор перемещения, компилятор создает неявный экземпляр, если не существует объявленного пользователем конструктора копии, оператора присваивания копирования, оператора присваивания перемещения или деструктора. Если явный или неявный конструктор перемещения не определен, операции, в которых в противном случае использовался конструктор перемещения, используют вместо этого конструктор копий. Если класс объявляет конструктор перемещения или оператор присваивания перемещения, неявно объявленный конструктор копии определяется как удаленный.
Неявно объявленный конструктор перемещения определяется как удаленный, если какие-либо члены, являющиеся типами классов, не имеют деструктора или компилятор не может определить, какой конструктор использовать для операции перемещения.
Дополнительные сведения о написании нетривиальных конструкторов перемещения см. в разделе конструкторы перемещения и операторы присваивания перемещения (C++).
Явно заданные по умолчанию и удаленные конструкторы
Можно явно заключать конструкторы копирования, конструкторы по умолчанию , конструкторы перемещения, операторы присваивания копирования, операторы присваивания перемещения и деструкторы. Вы можете явно Удалить все специальные функции элементов.
конструкторы constexpr
Конструктор может быть объявлен как constexpr , если
- Он либо объявляется по умолчанию, либо, в противном случае, удовлетворяет всем условиям для функций constexpr в целом;
- класс не имеет виртуальных базовых классов;
- Каждый из параметров имеет тип литерала;
- тело не является блоком функции try;
- все нестатические элементы данных и подобъекты базового класса инициализируются;
- Если класс имеет (a) объединение с вариантными членами или (b) имеет анонимные объединения, то инициализируется только один из членов Union.
- Каждый нестатический элемент данных типа класса, а все подобъекты базового класса имеют конструктор constexpr
Конструкторы списка инициализаторов
Если конструктор принимает в качестве параметра значение std: : initializer_list , а все другие параметры имеют аргументы по умолчанию, этот конструктор будет выбран в разрешении перегрузки при создании экземпляра класса с помощью прямой инициализации. Можно использовать initializer_list для инициализации любого участника, который может принять его. Например, предположим, что класс Box (показан ранее) имеет std::vector элемент m_contents . Вы можете предоставить конструктор следующим образом:
Затем создайте объекты Box следующим образом:
Явные конструкторы
Если у класса имеется конструктор с одним параметром, или у всех параметров, кроме одного, имеются значения по умолчанию, тип параметра можно неявно преобразовать в тип класса. Например, если у класса Box имеется конструктор, подобный следующему:
то возможно инициализировать объект Box следующим образом:
Или передать целое значение функции, принимающей объект Box:
В некоторых случаях подобные преобразования могут быть полезны, однако чаще всего они могут привести к незаметным, но серьезным ошибкам в вашем коде. В качестве общего правила следует использовать explicit ключевое слово в конструкторе (и определяемых пользователем операторах) для предотвращения такого рода неявного преобразования типов:
Когда конструктор является явным, эта строка вызывает ошибку компилятора: ShippingOrder so(42, 10.8); . Дополнительные сведения см. в разделе преобразования определяемого пользователем типа.
Порядок создания
Конструктор выполняет свою работу в следующем порядке.
Вызывает конструкторы базовых классов и членов в порядке объявления.
Если класс является производным от виртуальных базовых классов, конструктор инициализирует указатели виртуальных базовых классов объекта.
Если класс имеет или наследует виртуальные функции, конструктор инициализирует указатели виртуальных функций объекта. Указатели виртуальных функций указывают на таблицу виртуальных функций класса, чтобы обеспечить правильную привязку вызовов виртуальных функций к коду.
Выполняет весь код в теле функции.
В следующем примере показан порядок, в котором конструкторы базовых классов и членов вызываются в конструкторе для производного класса. Сначала вызывается конструктор базового класса, затем инициализируются члены базового класса в порядке их появления в объявлении класса. После этого вызывается конструктор производного класса.
Выходные данные будут выглядеть следующим образом.
Конструктор производного класса всегда вызывает конструктор базового класса, чтобы перед выполнением любых дополнительных операций иметь в своем распоряжении полностью созданные базовые классы. Конструкторы базовых классов вызываются в порядке наследования — например, если ClassA является производным от класса ClassB , производного от, то ClassC ClassC сначала вызывается конструктор, затем ClassB конструктор, а затем ClassA конструктор.
Если базовый класс не имеет конструктор по умолчанию, в конструкторе производного класса необходимо указать параметры конструктора базового класса.
Если конструктор создает исключение, то удаление выполняется в порядке, обратном созданию.
Отменяется код в теле функции конструктора.
Объекты базовых классов и объекты-члены удаляются в порядке, обратном объявлению.
Если конструктор не является делегирующим, удаляются все полностью созданные объекты базовых классов и объекты-члены. Однако поскольку сам объект создан не полностью, деструктор не выполняется.
Производные конструкторы и расширенная агрегатная инициализация
если конструктор базового класса не является открытым, но доступен для производного класса, то в режиме /std: c++ 17 в Visual Studio 2017 и более поздних версиях нельзя использовать пустые фигурные скобки для инициализации объекта производного типа.
В следующем примере показана соответствующая реакция на событие в C++14:
В C++17 Derived теперь считается агрегатным типом. Это означает, что инициализация Base через закрытый конструктор по умолчанию происходит непосредственно как часть расширенного правила агрегатной инициализации. Закрытый конструктор Base ранее вызывался через конструктор Derived , и это работало успешно благодаря объявлению дружественных отношений.
в следующем примере показано поведение c++ 17 в Visual Studio 2017 и более поздних версиях в /std: режим c++ 17 :
Конструкторы для классов с несколькими наследованиями
Если класс является производным от нескольких базовых классов, конструкторы базовых классов вызываются в том порядке, в котором они перечислены в объявлении производного класса.
Должны выводиться следующие выходные данные:
Делегирование конструкторов
Делегирующий конструктор вызывает другой конструктор в том же классе для выполнения некоторой работы по инициализации. Это полезно, если у вас есть несколько конструкторов, которые должны выполнять одинаковую работу. Можно написать основную логику в одном конструкторе и вызвать ее из других. В следующем тривиальном примере Box (int) делегирует свою работу Box (int, int, int):
Объект, созданный конструкторами, полностью инициализируется сразу после выполнения любого конструктора. Дополнительные сведения см. в разделе Делегирование конструкторов.
Наследование конструкторов (C++ 11)
Производный класс может наследовать конструкторы от прямого базового класса с помощью объявления, using как показано в следующем примере:
Visual Studio 2017 и более поздних версий: using инструкция в режиме /std: c++ 17 предоставляет все конструкторы из базового класса, за исключением тех, которые имеют идентичную сигнатуру для конструкторов в производном классе. Обычно, если в производном классе не объявляются новые данные-члены или конструкторы, оптимальным решением будет использовать наследуемые конструкторы.
Шаблон класса может наследовать все конструкторы от аргумента типа, если этот тип определяет базовый класс:
Производный класс не может наследовать от нескольких базовых классов, если у этих базовых классов есть конструкторы с идентичными сигнатурами.
Конструкторы и составные классы
Классы, содержащие члены типа класса, называются составными классами. При создании члена типа класса составного класса конструктор вызывается перед собственным конструктором класса. Если у содержащегося класса нет конструктора по умолчанию, необходимо использовать список инициализации в конструкторе составного класса. В предыдущем примере StorageBox при присвоении типу переменной-члена m_label нового класса Label необходимо вызвать конструктор базового класса и инициализировать переменную m_label в конструкторе StorageBox :
Источник