Выбрать главу

Account( const char*, double=0.0 );

Account( const string&, double=0.0 );

Account( const Account& );

// ...

private:

// ...

};

* Как правильно инициализировать член, являющийся объектом некоторого класса с собственным набором конструкторов? Этот вопрос можно разделить на три: где вызывается конструктор по умолчанию? Внутри конструктора по умолчанию класса Account;

* где вызывается копирующий конструктор? Внутри копирующего конструктора класса Account и внутри конструктора с двумя параметрами, принимающего в качестве первого тип string;

* как передать аргументы конструктору класса, являющегося членом другого класса? Это необходимо делать внутри конструктора Account с двумя параметрами, принимающего в качестве первого тип char*.

Решение заключается в использовании списка инициализации членов (мы упоминали о нем в разделе 14.2). Члены, являющиеся классами, можно явно инициализировать с помощью списка, состоящего из разделенных запятыми пар "имя члена/значение". Наш конструктор с двумя параметрами теперь выглядит так (напомним, что _name - это член, являющийся объектом класса string):

inline Account::

Account( const char* name, double opening_bal )

: _name( name ), _balance( opening_bal )

{

_acct_nmbr = het_unique_acct_nmbr();

}

Список инициализации членов следует за сигнатурой конструктора и отделяется от нее двоеточием. В нем указывается имя члена, а в скобках - начальные значения, что аналогично синтаксису вызова функции. Если член является объектом класса, то эти значения становятся аргументами, передаваемыми подходящему конструктору, который затем и используется. В нашем примере значение name передается конструктору string, который применяется к члену _name. Член _balance инициализируется значением opening_bal.

Аналогично выглядит второй конструктор с двумя параметрами:

inline Account::

Account( const string& name, double opening_bal )

: _name( name ), _balance( opening_bal )

{

_acct_nmbr = het_unique_acct_nmbr();

}

В этом случае вызывается копирующий конструктор string, инициализирующий член _name значением параметра name типа string.

Часто у новичков возникает вопрос: в чем разница между использованием списка инициализации и присваиванием значений членам в теле конструктора? Например, в чем разница между

inline Account::

Account( const char* name, double opening_bal )

: _name( name ), _balance( opening_bal )

{

_acct_nmbr = het_unique_acct_nmbr();

}

и

Account( const char* name, double opening_bal )

{

_name = name;

_balance = opening_bal;

_acct_nmbr = het_unique_acct_nmbr();

}

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

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

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

inline Account::

Account()

{

_name = "";

_balance = 0.0;

_acct_nmbr = 0;

}

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

Для объектов классов различие между инициализацией и присваиванием существенно. Член, являющийся объектом класса, всегда следует инициализировать с помощью списка, а не присваивать ему значение в теле конструктора. Более правильной является следующая реализация конструктора по умолчанию класса Account:

inline Account::

Account() : _name( string() )

{

_balance = 0.0;

_acct_nmbr = 0;