Инициализация объекта: параметры и инициализаторы

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

class ComplexType {
:::::
public:
ComplexType(double keyReal,
            double keyImag,
            char keyCTcharVal,
            int keyX);
:::::
};
:::::
ComplexType::ComplexType(double keyReal,
            double keyImag,
            char keyCTcharVal,
            int keyX)
 {
  cout << "This is ComplexType("
       << keyReal << ","
       << keyImag << ","
       << (int)keyCTcharVal << ","
       << keyX << ")" << endl;
  real = keyReal;
  imag = keyImag;
  CTcharVal = keyCTcharVal;
  x = keyX;
 };

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

ComplexType CDw2(100,100,0,0);
/* Создаётся объект типа ComplexType под именем CDw2 с
определёнными значениями. */
int iVal(10);
/* Аналогичным образом может быть определён и проинициализирован
объект основного типа */

Заметим, что к такому же результату (но только окольными путями) приводит и такая форма оператора определения:

ComplexType CDw2 = ComplexType(100,100,0,0);

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

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

ComplexType CDw1(); // Это ошибка!
int xVal();         // Это тоже не определение.

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

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

ComplexType(double keyReal = 0,
            double keyImag = 0,
            char keyCTcharVal = 0,
            int keyX = 0);

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

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

Внесём ещё одно изменение в текст нашей программы. На этот раз мы добавим спецификатор const в объявление данного-члена класса x:

class ComplexType
{
:::::
const int x;
:::::
}

И опять возникают новые проблемы. На этот раз они связаны с попыткой присвоения значения константе. Как известно, объявление данного-члена класса не допускает инициализации, а для того, чтобы константный член класса в процессе создания объекта всё же мог получить требуемое значение, в C++ используется так называемый ctorИнициализатор (именно так называется эта конструкция в справочном руководстве по C++ Б.Строуструппа). Мы не будем гадать, в чём заключается смысл этого названия, а лучше заново воспроизведем несколько форм Бэкуса-Наура.

ОпределениеФункции ::= [СписокСпецификаторовОбъявления]
                                                Описатель
                                                 [ctorИнициализатор]
                                                          ТелоФункции

ctorИнициализатор ::= : СписокИнициализаторовЧленовКласса

СписокИнициализаторовЧленовКласса ::= ИнициализаторЧленаКласса
                                [, СписокИнициализаторовЧленовКласса]

ИнициализаторЧленаКласса ::= ПолноеИмяКласса([СписокВыражений])
                         ::= Идентификатор([СписокВыражений])

ПолноеИмяКласса ::= КвалифицированноеИмяКласса
                ::= :: КвалифицированноеИмяКласса

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

ComplexType():x(1)
 {
  cout << "Здесь ComplexType():x(" << x << ")" << endl;
 };

Перед нами конструктор с ctorИнициализатором. Эта конструкция позволяет решать проблемы начальной инициализации константных данных-членов. При работе с данными-членами класса транслятор рассматривает операцию присвоения как изменение начального значения члена. Инициализатор же отвечает непосредственно за установку этого САМОГО ПЕРВОГО значения.

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

ComplexType():x(1), x(2) // Ошибка.
{
:::::
}

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

Добавим в объявление нашего класса объявление массива. Инициализация массива-члена класса при определении объекта не вызывает особых проблем (здесь следует вспомнить раздел, посвящённый массивам-параметрам). Однако в C++ отсутствует возможность инициализации нестатического константного массива-члена класса. Так что можно не стараться выписывать подобные объявления:

const int xx[2]; // Бессмысленное объявление.

всё равно массив xx[2] невозможно проинициализировать. Все варианты инициализации константного нестатического массива будут отвергнуты.

ComplexType():xx(1,2) {/*…*/};
ComplexType():xx({1,2}) {/*…*/};
ComplexType():xx[0](1), xx[1](2) {/*…*/};

Согласно БНФ, в состав инициализатора могут входить только имена или квалифицированные имена. Для обозначения элемента массива этого недостаточно. Как минимум, здесь требуется выражение индексации, которое указывало бы номер элемента массива.

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

:::::
const int DefVal[2] = {1,2};
class ComplexType
{
:::::
const int const * px;
/* Объявили константный указатель на константу. */
:::::
ComplexType():px(DefVal) {/*…*/};
:::::
};

Окольными путями мы всё же достигаем желаемого результата. Константный указатель на константу контролирует константный массив.

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

ComplexType():px(DefVal),
              x(px[0]), // Транслятор уже знает, что такое px.
             CTcharVal(32),
             real(100),
             imag(real/25) // И здесь тоже всё в порядке.
{
// Здесь располагается тело конструктора.
:::::
}

Назад | Содержание | Вперед