С инициализация массива в конструкторе

Обновлено: 19.04.2024

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

Когда выполняется конструктор класса, создаются m_value1 , m_value2 и m_value3 . Затем запускается тело конструктора, в котором переменным-членам данных присваиваются значения. Это похоже на выполнение следующего кода в не объектно-ориентированном C++:

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

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

Это создает код, подобный следующему:

Присваивание значений константным или ссылочным переменным-членам в теле конструктора в некоторых случаях невозможно.

Списки инициализаторов членов

Чтобы решить эту проблему, C++ предоставляет метод инициализации переменных-членов класса (вместо присваивания им значений после их создания) через список инициализаторов членов (часто называемый «списком инициализации членов»). Не путайте их с похоже называющимся списком инициализаторов, который мы можем использовать для присваивания значений массивам.

В уроке «1.4 – Присваивание и инициализация переменных» вы узнали, что переменные можно инициализировать тремя способами: через копирующую, прямую и унифицированную инициализацию.

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

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

Теперь давайте напишем тот же код, используя список инициализации:

Эта программа печатает:

Список инициализаторов членов класса вставляется после параметров конструктора. Он начинается с двоеточия ( : ), а затем перечисляет через запятые все иницализируемые переменные вместе со значениями этих переменных.

Обратите внимание, что нам больше не нужно выполнять присваивание в теле конструктора, поскольку список инициализаторов заменяет эту функцию. Также обратите внимание, что список инициализаторов не заканчивается точкой с запятой.

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

Эта программа печатает:

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

Вот пример класса с константной переменной-членом:

Это работает, потому что нам разрешено инициализировать константные переменные (но не присваивать им значения!).

Правило

Для инициализации переменных-членов вашего класса вместо присваивания используйте списки инициализаторов членов.

Инициализация элементов массива списками инициализаторов членов

Рассмотрим класс с членом-массивом:

До C++11 член-массив с помощью списка инициализации членов класса можно было только обнулить:

Однако, начиная с C++11, вы можете полностью инициализировать член-массив, используя унифицированную инициализацию:

Инициализация переменных-членов, которые являются классами

Список инициализации членов также может использоваться для инициализации членов, которые являются классами.

Эта программа печатает:

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

Форматирование списков инициализаторов

C++ дает вам большую гибкость в том, как форматировать списки инициализаторов, и вам решать, как вы хотите действовать. Но вот несколько рекомендаций:

Если список инициализаторов умещается в той же строке, что и имя функции, то можно разместить всё в одной строке:

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

Если все инициализаторы не помещаются в одну строку (или инициализаторы нетривиальны), вы можете разделить их, по одному на строку:

Порядок в списке инициализаторов

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

Для достижения наилучших результатов следует соблюдать следующие рекомендации:

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

Резюме

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

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

Вопрос 1

Если вам нужно напоминание о том, как использовать целые числа фиксированной ширины, просмотрите урок «4.6 – Целочисленные типы фиксированной ширины и size_t».

Подсказка: если ваша функция print() работает некорректно, убедитесь, что вы приводите uint_fast8_t к типу int .

I'm having a brain cramp. how do I initialize an array of objects properly in C++?

edit: Wild & crazy workaround ideas are appreciated, but they won't help me in my case. I'm working on an embedded processor where std::vector and other STL constructs are not available, and the obvious workaround is to make a default constructor and have an explicit init() method that can be called after construction-time, so that I don't have to use initializers at all. (This is one of those cases where I've gotten spoiled by Java's final keyword + flexibility with constructors.)

Wouldn't it be easier to use struct in place of class for pedagogical simplicity? I find code that compiles easier to learn from ;-)

When I copied your code in to my compiler I had to add what you left out. So for pedagogical simplicity you might consider not making it difficult for people to help you in the future.

14 Answers 14

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

There is no way. You need a default constructor for array members and it will be called, afterwards, you can do any initialization you want in the constructor.

Unfortunately, you're right. +1 Note that C++1x' unified initialization syntax will allow you to do this.

Just to update this question for C++11, this is now both possible to do and very natural:

Those braces can also be elided for an even more concise:

Which can easily be extended to multi-dimensional arrays too:

Right now, you can't use the initializer list for array members. You're stuck doing it the hard way.

In C++0x you can write:

interesting. I probably should have used something besides int then in my example, as it's too "easy" to deal with. :-)

Unfortunately there is no way to initialize array members till C++0x.

You could use a std::vector and push_back the Foo instances in the constructor body.

You could give Foo a default constructor (might be private and making Baz a friend).

You could use an array object that is copyable (boost or std::tr1) and initialize from a static array:

+1. Wondered why noone came up with this, until i saw your answer. array is trivial to implement, and it's neither wild nor crazy. You could write a function like array create() < arraya = < . >; return a; > to avoid the static variable, too.

Seems obvious to me too, even if the support for templates is weak on the target compiler (no std::vector seems fishy) a generation approach would work (preprocessor or script generating necessary classes).

You can use C++0x auto keyword together with template specialization on for example a function named boost::make_array() (similar to make_pair() ). For the case of where N is either 1 or 2 arguments we can then write variant A as

and variant B as

GCC-4.6 with -std=gnu++0x and -O3 generates the exact same binary code for

using both A and B as it does for

For user defined types (UDT), though, variant B results in an extra copy constructor, which usually slow things down, and should therefore be avoided.

Note that boost::make_array errors when calling it with explicit char array literals as in the following case

I believe this is a good thing as const char* literals can be deceptive in their use.

Variadic templates, available in GCC since 4.5, can further be used reduce all template specialization boiler-plate code for each N into a single template definition of boost::make_array() defined as

This works pretty much as we expect. The first argument determines boost::array template argument T and all other arguments gets converted into T . For some cases this may undesirable, but I'm not sure how if this is possible to specify using variadic templates.

Массив — это последовательность объектов того же типа, которые занимают непрерывную область памяти. Традиционные массивы в стиле C являются источником многих ошибок, но по-прежнему являются распространенными, особенно в старых базах кода. В современном C++ настоятельно рекомендуется использовать std::vector или std::array вместо массивов в стиле C, описанных в этом разделе. Оба этих стандартных типа библиотек хранят свои элементы в виде непрерывного блока памяти. Однако они обеспечивают гораздо большую безопасность типов и поддерживают итераторы, которые гарантированно указывают на допустимое расположение в последовательности. Дополнительные сведения см. в разделе "Контейнеры".

Объявления стека

В объявлении массива C++ размер массива указывается после имени переменной, а не после имени типа, как в некоторых других языках. В следующем примере для стека объявляется массив из 1000 двойников. Число элементов должно быть предоставлено в виде целочисленного литерала или в виде константного выражения. Это связано с тем, что компилятору необходимо знать, сколько пространства стека необходимо выделить; Он не может использовать значение, вычисленное во время выполнения. Каждому элементу в массиве присваивается значение по умолчанию 0. Если вы не назначаете значение по умолчанию, каждый элемент изначально содержит любые случайные значения, которые будут находиться в этом расположении памяти.

Первый элемент в массиве является нулевым элементом. Последним элементом является элемент (n-1), где n — количество элементов, которые может содержать массив. Число элементов в объявлении должно быть целочисленным типом и должно быть больше 0. Ваша ответственность заключается в том, чтобы программа никогда не передает значение оператору подстрока, который больше (size - 1) .

Массив нулевого размера является допустимым только в том случае, если массив является последним полем в struct или union если расширения Майкрософт включены ( /Za или /permissive- не заданы).

Массивы на основе стека быстрее выделяют и получают доступ, чем массивы на основе кучи. Однако пространство стека ограничено. Количество элементов массива не может быть настолько большим, что использует слишком много памяти стека. Сколько слишком много зависит от вашей программы. Средства профилирования можно использовать для определения того, слишком ли большой массив.

Объявления кучи

Может потребоваться слишком большой массив для выделения в стеке или размер которого неизвестлен во время компиляции. Этот массив можно выделить в куче с помощью new[] выражения. Оператор возвращает указатель на первый элемент. Оператор подстрока работает с переменной указателя так же, как и в массиве на основе стека. Можно также использовать арифметические указатели для перемещения указателя на любые произвольные элементы в массиве. Вы несете ответственность за обеспечение следующего:

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

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

Инициализация массивов

Массив можно инициализировать в цикле, по одному элементу за раз или в одной инструкции. Содержимое следующих двух массивов идентично:

Передача массивов в функции

Когда массив передается функции, он передается в качестве указателя на первый элемент, будь то массив на основе стека или кучи. Указатель не содержит дополнительных сведений о размере или типе. Такое поведение называется разложением указателя. При передаче массива в функцию необходимо всегда указывать количество элементов в отдельном параметре. Это также означает, что элементы массива не копируются при передаче массива в функцию. Чтобы предотвратить изменение элементов функцией, укажите параметр в качестве указателя на const элементы.

В следующем примере показана функция, которая принимает массив и длину. Указатель указывает на исходный массив, а не копию. Так как параметр не const задан, функция может изменять элементы массива.

Объявите и определите параметр p массива так, const чтобы он был доступен только для чтения в блоке функции:

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

Многомерные массивы

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

Он задает массив типа int , концептуально упорядоченный в двумерной матрице из пяти строк и семи столбцов, как показано на следующем рисунке:


Концептуальная структура многомерного массива

Можно объявить многомерные массивы с списком инициализаторов (как описано в инициализаторах). В этих объявлениях можно опустить константное выражение, указывающее границы для первого измерения. Пример:

В показанном выше объявлении определяется массив, состоящий из трех строк и четырех столбцов. Строки представляют фабрики, а столбцы — рынки, на которые фабрики поставляют свою продукцию. Значения — это стоимости транспортировки с фабрик на рынки. Первое измерение массива опущено, но компилятор заполняет его, проверяя инициализатор.

Использование оператора косвенного обращения (*) в типе n-мерного массива приводит к получению массива n-1. Если n равно 1, создается скаляр (или элемент массива).

Массивы C++ размещаются в памяти по срокам. Построчный порядок означает, что быстрее всего изменяется последний индекс.

Пример

Можно также опустить спецификацию границ для первого измерения многомерного массива в объявлениях функций, как показано ниже:

Эта функция FindMinToMkt записывается таким образом, что добавление новых фабрик не требует каких-либо изменений в коде, а просто перекомпиляции.

Инициализация массивов

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

Рассмотрим класс Point , определяющий два конструктора:

Первый элемент aPoint создается с помощью конструктора Point( int, int ) , а оставшиеся два элемента — с помощью конструктора по умолчанию.

Статические массивы элементов (независимо от того, можно ли const ) инициализировать в их определениях (за пределами объявления класса). Пример:

Доступ к элементам массива

К отдельным элементам массива можно обращаться при помощи оператора индекса массива ( [ ] ). Если вы используете имя одномерного массива без подстрока, оно вычисляется как указатель на первый элемент массива.

Если используются многомерные массивы, в выражениях можно использовать различные сочетания.

В приведенном выше коде multi представляет собой трехмерный массив типа double . Указатель p2multi указывает на массив типа double размера 3. В этом примере массив используется с одним, двумя и тремя индексами. Хотя обычно указываются все подстроки, как в инструкции cout , иногда бывает полезно выбрать определенное подмножество элементов массива, как показано в следующих cout инструкциях.

Перегруженный оператор подстрочного индекса

Как и другие операторы, оператор подстрока ( [] ) может быть переопределен пользователем. Поведение оператора индекса по умолчанию, если он не перегружен, — совмещать имя массива и индекс с помощью следующего метода.

Как и во всех дополнениях, включающих типы указателей, масштабирование выполняется автоматически для настройки размера типа. Результирующая величина не является n байтами из источника array_name ; вместо этого это n-й элемент массива. Дополнительные сведения об этом преобразовании см. в разделе "Аддитивные операторы".

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

((array_name) + (subscript1 * max2 * max3 * . * maxn) + (subscript2 * max3 * . * maxn) + . + subscriptn))

Массивы в выражениях

Если идентификатор типа массива отображается в выражении, отличном от sizeof адреса ( & ) или инициализации ссылки, он преобразуется в указатель на первый элемент массива. Пример:

Указатель psz указывает на первый элемент массива szError1 . Массивы, в отличие от указателей, не являются изменяемыми l-значениями. Вот почему следующее назначение является незаконным:

Инициализатор определяет начальное значение переменной. Можно инициализировать переменные в этих контекстах:

В определении переменной:

В качестве одного из параметров функции:

В качестве возвращаемого типа функции:

Инициализаторы могут принимать эти формы:

Выражение (или разделенный запятыми список выражений) в скобках:

Знак равенства с последующим выражением:

Список инициализации в фигурных скобках. Список может быть пустым или может состоять из набора списков как в приведенном ниже примере.

Типы инициализации

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

Нулевая инициализация

Нулевая инициализация — задание для переменной нулевого значения, неявно преобразованного в тип:

Числовые переменные инициализируются значением 0 (или 0,0; 0,0000000000 и т.п.).

Переменные char инициализируются в '\0' .

Указатели инициализируются в nullptr .

Массивы, классы POD , структуры и объединения инициализируют свои члены равным нулю.

Нулевая инициализация выполняется в разное время:

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

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

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

Ниже приведены некоторые примеры нулевой инициализации:

Инициализация по умолчанию

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

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

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

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

Если члены массива не имеют конструктор по умолчанию, компилятор выдает ошибку.

Инициализация по умолчанию константных переменных

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

Инициализация по умолчанию статических переменных

Статические переменные, объявленные без инициализатора, инициализируются значением 0 (с неявным преобразованием к соответствующему типу).

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

Инициализация значения

Инициализация значения происходит в следующих случаях:

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

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

Объект инициализируется ключевым словом new и пустыми скобками или фигурными скобками

При инициализации значения выполняются следующие действия:

Для классов, имеющих хотя бы один открытый конструктор, вызывается конструктор по умолчанию.

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

В случае массивов каждый элемент инициализируется значением.

Во всех остальных случаях переменная инициализируется нулевым значением.

Инициализация копированием

Инициализация копированием — это инициализация одного объекта с использованием другого объекта. Она выполняется в следующих случаях:

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

Аргумент передается в функцию.

Объект возвращается функцией.

Возникает или перехватывается исключение.

Нестатический элемент данных инициализируется с помощью знака равенства.

Класс, структура и члены объединения инициализируются с применением инициализации путем копирования во время агрегатной инициализации. Примеры см. в разделе "Статистическая инициализация ".

Следующий код демонстрирует несколько примеров инициализации копированием.

Инициализация копированием не может вызывать явные конструкторы.

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

Прямая инициализация

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

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

Переменная инициализируется ключевым словом new плюс непустые скобки или скобки

Переменная инициализируется с помощью . static_cast

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

В копии захваченной переменной в лямбда-выражении.

Приведенный ниже код демонстрирует несколько примеров прямой инициализации.

Инициализация списка

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

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

Объект возвращается функцией.

Аргумент передается функции.

Один из аргументов при прямой инициализации.

В инициализаторе нестатических элементов данных.

В списке инициализации конструктора.

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

Агрегатная инициализация

Агрегатная инициализация — форма инициализации списка для массивов и типов классов (часто структур и объединений), со следующими характеристиками:

Отсутствие закрытых или защищенных членов.

Отсутствие заданных пользователем конструкторов кроме явно заданных по умолчанию или удаленных конструкторов.

Отсутствие базовых классов.

Отсутствие виртуальных функций-членов.

В Visual Studio 2015 и более ранних версиях агрегат не может иметь инициализаторы фигурных скобок или равных значений для нестатических элементов. Это ограничение было удалено в стандарте C++14 и реализовано в Visual Studio 2017 г.

Агрегатные инициализаторы состоят из списка инициализации в фигурных скобках со знаком равенства или без него как в приведенном ниже примере:

Вы должны увидеть следующий результат.

Элементы массива, объявленные, но не инициализированные явно во время статистической инициализации, инициализируются с нуля, как показано myArr3 выше.

Инициализация объединений и структур

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

Инициализация статистических выражений, содержащих статистические выражения

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

Инициализация ссылок

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

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

Хотя синтаксис может быть одинаковым, инициализация переменных ссылочного типа и присваивание значений переменным ссылочного типа семантически различаются. В предыдущем примере присваивания, которые изменяют значения переменных iVar и lVar , выглядят аналогично инициализации, но имеют другой эффект. Инициализация определяет объект, на который указывает переменная ссылочного типа; при присваивании через ссылку производится присваивание значения объекту, на который указывает ссылка.

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

Переменные ссылочного типа можно объявлять без инициализаторов только в указанных ниже случаях.

Объявления функций (прототипы). Пример:

Объявления типов значений, возвращаемых функцией. Пример:

Объявления члена класса ссылочного типа. Пример:

Объявление переменной, явно указанной как extern . Пример:

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


Граф принятия решений для инициализации ссылочных типов

Ссылки на volatile типы (объявленные как volatile typenameidentifier &) можно инициализировать с volatile объектами того же типа или с объектами, которые не были объявлены как volatile . Однако они не могут быть инициализированы объектами const этого типа. Аналогичным образом ссылки на const типы (объявленные как const typenameidentifier &) можно инициализировать с const объектами того же типа (или с объектами, которые не были объявлены как const ). Однако они не могут быть инициализированы объектами volatile этого типа.

Ссылки, которые не соответствуют ни ключевому слову const volatile , либо могут быть инициализированы только с объектами, объявленными как ни один из const volatile них.

Инициализация внешних переменных

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

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

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

При объявлении экземпляра класса компилятор выбирает, какой конструктор будет вызываться на основе правил разрешения перегрузки:

  • Конструкторы могут быть объявлены как inline , , explicitfriend или constexpr .
  • Конструктор может инициализировать объект, объявленный как const , volatile или const volatile . Объект становится const после завершения конструктора.
  • Чтобы определить конструктор в файле реализации, присвойте ему полное имя, как и любая другая функция-член: Box::Box() .

Списки инициализаторов элементов

При необходимости конструктор может иметь список инициализаторов элементов, который инициализирует члены класса перед запуском тела конструктора. (Список инициализаторов элементов не совпадает со списком инициализаторов типа std::initializer_list .)

Предпочитать инициализаторы элементов перечисляют значения вместо назначения значений в тексте конструктора. Список инициализаторов элементов напрямую инициализирует элементы. В следующем примере показан список инициализаторов элементов, состоящий из всех identifier(argument) выражений после двоеточия:

Идентификатор должен ссылаться на член класса; он инициализирован со значением аргумента. Аргумент может быть одним из параметров конструктора, вызова функции или . std::initializer_list

const члены и члены ссылочного типа должны быть инициализированы в списке инициализаторов элементов.

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

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

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

Конструкторы по умолчанию являются одной из специальных функций-членов. Если конструкторы в классе не объявляются, компилятор предоставляет неявный inline конструктор по умолчанию.

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

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

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

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

Это утверждение является примером проблемы "Большинство vexing Parse". Можно интерпретировать myclass md(); как объявление функции или как вызов конструктора по умолчанию. Поскольку средства синтаксического анализа C++ предпочитают объявления по сравнению с другими вещами, выражение рассматривается как объявление функции. Дополнительные сведения см. в разделе "Большинство синтаксического анализа".

Если объявлены какие-либо конструкторы, отличные от по умолчанию, компилятор не предоставляет конструктор по умолчанию:

Если у класса нет конструктора по умолчанию, массив объектов этого класса нельзя создать с помощью синтаксиса квадратной скобки. Например, учитывая предыдущий блок кода, массив Boxes нельзя объявить следующим образом:

Однако для инициализации массива объектов Box можно использовать набор списков инициализаторов:

Дополнительные сведения см. в разделе "Инициализаторы".

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

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

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

При определении конструктора копирования необходимо также определить оператор присваивания копирования (=). Дополнительные сведения см. в разделе "Назначение " и " Копирование конструкторов" и операторов присваивания копирования.

Вы можете запретить копирование объекта, определив конструктор копирования как удаленный:

При попытке копирования объекта возникает ошибка C2280: попытка ссылаться на удаленную функцию.

Конструкторы перемещения

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

Компилятор выбирает конструктор перемещения, когда объект инициализируется другим объектом того же типа, если другой объект будет уничтожен и больше не нуждается в его ресурсах. В следующем примере показано одно дело, когда конструктор перемещения выбирается с помощью разрешения перегрузки. В конструкторе, который вызывает get_Box() , возвращаемое значение является xvalue (значение eXpiring). Поэтому он не назначается какой-либо переменной и поэтому выходит за пределы области действия. Чтобы обеспечить мотивацию для этого примера, давайте предоставим Box большой вектор строк, представляющих его содержимое. Вместо копирования вектора и его строк конструктор перемещения "крадет" его из значения "box", чтобы вектор теперь принадлежит новому объекту. Вызов std::move необходим, так как оба vector класса string реализуют собственные конструкторы перемещения.

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

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

Дополнительные сведения о написании конструктора нетривиального перемещения см. в разделе "Конструкторы перемещения" и "Операторы присваивания перемещения" (C++).

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

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

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

Конструктор может быть объявлен как constexpr , если

  • он либо объявлен как стандартный, либо удовлетворяет всем условиям для функций constexpr в целом;
  • класс не имеет виртуальных базовых классов;
  • каждый из параметров является литеральным типом;
  • тело не является блоком try-block функции;
  • инициализированы все нестатические члены данных и подобъекты базового класса;
  • Значение , если класс является (a) объединением, имеющим члены варианта, или (б) имеет анонимные объединения, инициализируется только один из членов профсоюза;
  • каждый нестатический член данных типа класса, а все подобъекты базового класса имеют конструктор constexpr.

Конструкторы списков инициализаторов

Затем создайте объекты Box следующим образом:

Явные конструкторы

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

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

Или передать целое значение функции, принимающей объект Box:

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

Когда конструктор является явным, эта строка вызывает ошибку компилятора: ShippingOrder so(42, 10.8); . Дополнительные сведения см. в разделе о преобразованиях определяемых пользователем типов.

Порядок строительства

Конструктор выполняет свою работу в следующем порядке.

Вызывает конструкторы базовых классов и членов в порядке объявления.

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

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

Выполняет весь код в теле функции.

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

Выходные данные будут выглядеть следующим образом.

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

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

Если конструктор создает исключение, то удаление выполняется в порядке, обратном созданию.

Отменяется код в теле функции конструктора.

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

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

Производные конструкторы и расширенная инициализация агрегатов

Если конструктор базового класса не является открытым, но доступен для производного класса, нельзя использовать пустые фигурные скобки для инициализации объекта производного типа в /std:c++17 режиме, а затем в Visual Studio 2017 и более поздних версий.

В следующем примере показана соответствующая реакция на событие в C++14:

В C++17 Derived теперь считается агрегатным типом. Это означает, что инициализация Base через закрытый конструктор по умолчанию происходит непосредственно как часть расширенного правила агрегатной инициализации. Ранее частный Base конструктор был вызван через Derived конструктор, и он был успешно выполнен из-за friend объявления.

В следующем примере показано поведение 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 :

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