С builder конструктор класса

Обновлено: 15.05.2024

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

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

date today = date(6,4,2014); // полная форма
date xmas(25,12,0); // сокращенная форма
// date my_burthday; // недопустимо, опущена инициализация

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

class date <
int month, day, year;
public :
date( int , int , int ); // день месяц год
date( char *); // дата в строковом представлении
date(); // дата по умолчанию: сегодня
>;

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

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

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

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

class date
int month, day, year;
public :
date( int , int , int );
date( char *);
date(); // конструктор по умолчанию
>;

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

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

  • формальный параметр – объект, передаваемый по значению, создается в стеке в момент вызова функции и инициализируется копией фактического параметра;
  • результат функции – объект, передаваемый по значению, в момент выполнения оператора return копируется во временный объект, сохраняющий результат функции.

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

  • date2 в приведенном определении;
  • для создаваемого в стеке формального параметра;
  • для временного объекта, сохраняющего значение, возвращаемое функцией.

Вместо этого в них копируется содержимое объекта-источника:

  • date1 в приведенном примере;
  • фактического параметра;
  • объекта-результата в операторе return .

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

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

Деструкторы

Определяемый пользователем класс имеет конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие. Деструктор обеспечивает соответствующую очистку объектов указанного типа. Имя деструктора представляет собой имя класса с предшествующим ему знаком «тильда» ~ . Так, для класса X деструктор будет иметь имя ~X() . Многие классы используют динамическую память, которая выделяется конструктором, а освобождается деструктором.

class date
int day, year;
char *month;
public :
date( int d, char * m, int y)
day = d;
month = new char [strlen(m)+1];
strcpy_s(month, strlen(m)+1,m);
year = y;
>
~date() < delete [] month; >// деструктор
>;

Поля, имеющие тип класса

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

Конструктор нового класса имеет пустое тело и список вызываемых конструкторов класса vect , перечисленных после двоеточия (:) через запятую (,). Они выполняются с целым аргументом i , создавая 3 объекта класса vect: a, b, c .

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

Здравствуйте, у меня такой вопрос. В коде я не нашел сам деструктор. Он создается и запускается сам после выхода из блока main ?

Чтобы настроить, как класс инициализирует его члены или вызывать функции при создании объекта класса, определите конструктор. Конструкторы имеют имена, совпадающие с именами классов, и не имеют возвращаемых значений. Вы можете определить столько перегруженных конструкторов, сколько необходимо для настройки инициализации различными способами. Как правило, конструкторы имеют открытые специальные возможности, чтобы код за пределами определения класса или иерархии наследования может создавать объекты класса. Но вы также можете объявить конструктор как 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 :

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

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

Перегрузки

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

Инициализирует новый экземпляр класса StringBuilder, используя указанную емкость.

Инициализирует новый экземпляр класса StringBuilder, используя указанную строку.

Инициализирует новый экземпляр класса StringBuilder, который начинается с указанной емкости и может увеличиваться до указанного максимального значения.

Инициализирует новый экземпляр класса StringBuilder, используя указанную строку и емкость.

Инициализирует новый экземпляр класса StringBuilder из указанной подстроки и емкости.

StringBuilder()

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

Примеры

В следующем примере показано, как вызвать StringBuilder конструктор без параметров.

Комментарии

Строковое значение этого экземпляра имеет String.Emptyзначение, а емкость задается в качестве емкости по умолчанию для конкретной реализации.

Применяется к

StringBuilder(Int32)

Инициализирует новый экземпляр класса StringBuilder, используя указанную емкость.

Параметры

Предлагаемый начальный размер этого экземпляра.

Исключения

Значение параметра capacity меньше нуля.

Примеры

В следующем примере показано, как вызвать StringBuilder конструктор с указанной емкостью.

Комментарии

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

Строковое значение этого экземпляра имеет значение String.Empty. Если capacity значение равно нулю, используется емкость по умолчанию для конкретной реализации.

См. также раздел

Применяется к

StringBuilder(String)

Инициализирует новый экземпляр класса StringBuilder, используя указанную строку.

Параметры

Строка, используемая для инициализации значения экземпляра. Если value равно null , то новый StringBuilder будет содержать пустую строку (то есть, он содержит Empty).

Примеры

В следующем примере показано, как вызвать StringBuilder конструктор с указанной строкой.

Комментарии

Если value равно null , то новый StringBuilder будет содержать пустую строку (то есть, он содержит Empty).

Применяется к

StringBuilder(Int32, Int32)

Инициализирует новый экземпляр класса StringBuilder, который начинается с указанной емкости и может увеличиваться до указанного максимального значения.

Параметры

Предлагаемый начальный размер StringBuilder.

Наибольшее допустимое количество знаков в текущей строке.

Исключения

maxCapacity меньше единицы, capacity меньше нуля, или capacity больше maxCapacity .

Примеры

В следующем примере показано, как вызвать StringBuilder конструктор с указанной емкостью и максимальной емкостью.

Комментарии

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

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

Свойство maxCapacity определяет максимальное количество символов, которое может содержать текущий экземпляр. Его значение присваивается свойству MaxCapacity . Если число символов, хранящихся в текущем экземпляре, превышает это maxCapacity значение, StringBuilder объект не выделяет дополнительную память, а создает исключение.

Примечания для тех, кто вызывает этот метод

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


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

Традиционно, программисты использовали Telescoping Constructor паттерн. Суть этого паттерна состоит в том, что Вы предоставляете несколько конструкторов: конструктор с обязательными параметрами, конструктор с одним дополнительным параметром, конструктор с двумя дополнительными параметрами, и так далее. Продемонстрируем как это будет выглядеть на практике. Для краткости будем использовать только 4 дополнительных параметра.

// Telescoping constructor pattern - плохо масштабируемый!
public class NutritionFacts

private final int servingSize; // обязательный параметр
private final int servings; // обязательный параметр
private final int calories; // дополнительный параметр
private final int fat; // дополнительный параметр
private final int sodium; // дополнительный параметр
private final int carbohydrate; // дополнительный параметр

public NutritionFacts( int servingSize, int servings) this (servingSize, servings, 0);
>

public NutritionFacts( int servingSize, int servings, int calories) this (servingSize, servings, calories, 0);
>

public NutritionFacts( int servingSize, int servings, int calories, int fat) this (servingSize, servings, calories, fat, 0);
>

public NutritionFacts( int servingSize, int servings, int calories, int fat,
int sodium) this (servingSize, servings, calories, fat, sodium, 0);
>

public NutritionFacts( int servingSize, int servings, int calories, int fat,
int sodium, int carbohydrate) this .servingSize = servingSize;
this .servings = servings;
this .calories = calories;
this .fat = fat;
this .sodium = sodium;
this .carbohydrate = carbohydrate;
>
>

Когда Вы хотите создать объект данного класса, Вы используете конструктор с необходимым списком параметров:

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

Обычно для вызова конструктора потребуется передавать множество параметров, которые Вы не хотите устанавливать, но Вы в любом случае вынуждены передать для них значение. В нашем случае, мы установили значение 0 для поля fat . Поскольку мы имеем только шесть параметров, может показаться, что это не так уж и плохо. Но это начинает доставлять огромные проблемы когда число параметров увеличивается.


Короче говоря, используя Telescoping Constructor паттерн, становится трудно писать код клиента, когда имеется много параметров, а еще труднее этот код читать. Читателю остается только гадать, что означают все эти значения и нужно тщательно высчитывать позицию параметра, чтобы выяснить к какому полю он относится. Длинные последовательности одинаково типизированных параметров могут причинить тонкие ошибки. Если клиент случайно перепутает два из таких параметров, то компиляция будет успешной, но программа будет работать не верно.

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

// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts // Параметры инициализируются значениями по умолчанию
private int servingSize = -1; // Обязательный
private int servings = -1; // Обязательный
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

// Сеттеры
public void setServingSize( int val) servingSize = val;
>

public void setServings( int val) servings = val;
>

public void setCalories( int val) calories = val;
>

public void setFat( int val) fat = val;
>

public void setSodium( int val) sodium = val;
>

public void setCarbohydrate( int val) carbohydrate = val;
>
>

Данный подход лишен недостатков Telescoping Constructor паттерна (Объект легко создавать и полученный код легко читать):

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

К сожалению, JavaBeans паттерн не лишен серьезных недостатков. Поскольку строительство разделено между несколькими вызовами, JavaBean может находиться в неустойчивом состоянии частично пройдя через конструирование. Попытка использования объекта, если он находится в неустойчивом состоянии может привести к ошибкам, которые далеки от кода, содержащего ошибку, следовательно, трудными для отладки. Также JavaBeans паттерн исключает возможность сделать класс неизменным(immutable), что требует дополнительных усилий со стороны программиста для обеспечения безопасности в многопоточной среде.

К счастью, есть и третья альтернатива, которая сочетает в себе безопасность паттерна Telescoping Constructor с читаемостью паттерна JavaBeans. Она является одной из форм паттерна Builder(Строитель). Вместо непосредственного создания желаемого объекта, клиент вызывает конструктор (или статическую фабрику) со всеми необходимыми параметрами и получает объект строителя. Затем клиент вызывает сеттер-подобные методы у объекта строителя для установки каждого дополнительного параметра. Наконец, клиент вызывает метод build() для генерации объекта, который будет являться неизменным(immutable). Строитель является статическим внутренним классом в классе, который он строит. Вот как это выглядит на практике:

// паттерн Builder
public class NutritionFacts private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public static class Builder // Обязательные параметры
private final int servingSize;
private final int servings;
// Дополнительные параметры - инициализируются значениями по умолчанию
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;

public Builder( int servingSize, int servings) this .servingSize = servingSize;
this .servings = servings;
>

public Builder calories( int val) calories = val;
return this ;
>

public Builder fat( int val) fat = val;
return this ;
>

public Builder carbohydrate( int val) carbohydrate = val;
return this ;
>

public Builder sodium( int val) sodium = val;
return this ;
>

public NutritionFacts build() return new NutritionFacts( this );
>
>

private NutritionFacts(Builder builder) servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
>
>

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

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();

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


UPDATE
Согласно комментарию предлагается:
для рассматриваемого класса NutritionFacts логично предусмотреть getter'ы для неизменяемых полей. Иначе получается, что объект-то мы построим, а воспользоваться им не сможем.
Поля Builder'а логичнее именовать в соответствием с конвенцией JavaBeans, а именно setXXX(). Поскольку данный способ является уже стандартом де-факто для Java, подобный подход улучшит читабельность кода.

Из-за чего компилятор выдаёт предупреждения в файле Patient.cpp на определении двух конструкторов класса и как это пофиксить?

Выдаёт два таких предупреждения:

Warning C26495 Variable 'Patient::id' is uninitialized. Always initialize a member variable (type.6).

Код файла Patient.h:

Код файла Patient.cpp:

Error C2274 '->': illegal as right side of '.' operator Lab10

При попытке заменить -> на точку поддчеркивает this и выдаёт:

expression must have class type

введите сюда описание изображения

конструктор должен инциализировать ВСЕ поля, иначе нет инварианта, обьект какраз не будет создан, как бы вы не пытались вывести на экран, что обьект создан

"добавил инициализацию, но предупрждения остались" - вы выдумываете. И что значит "новая ошибка"? Где код, в котором возникла эта "ошибка"? Откуда мы должны знать, что вы там еще понаписывали?

2 ответа 2

В конструкторе Patient::Patient() у вас поля инициализируются конструктором по-умолчанию, так что поле int id; с тривиальным конструктором остается неинициализированным (то бишь заполнено мусором). Соответственно его необходимо инициализировать:

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

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

то есть "m_" нужно для различия между тем, что прописано в параметрах конструктора и полями самого класса?

А почему для пустого конструктора Patient::Patient() достаточно проинициализоровать только одну переменную для устранения предупреждения?

@iwannaknow Потому что у полей - экземпляров класса std::string конструктор по-умолчанию не тривиальный и инициализирует эти поля.

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