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

}

Мы удалили ненужное присваивание _name из тела конструктора. Явный же вызов конструктора по умолчанию string излишен. Ниже приведена эквивалентная, но более компактная версия:

inline Account::

Account()

{

_balance = 0.0;

_acct_nmbr = 0;

}

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

// предпочтительный стиль инициализации

inline Account::

Account() : _balance( 0.0 ), _acct_nmbr( 0 )

{}

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

class ConstRef {

public:

ConstRef(int ii );

private:

int i;

const int ci;

int

};

ConstRef::

ConstRef( int ii )

{ // присваивание

i = ii; // правильно

ci = ii; // ошибка: нельзя присваивать константному члену

ri = i; // ошибка: ri не инициализирована

}

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

// правильно: инициализируются константные члены и ссылки

ConstRef::

ConstRef( int ii )

: ci( ii ), ri ( i )

{ i = ii; }

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

class Account {

public:

// ...

private:

unsigned int _acct_nmbr;

double _balance;

string _name;

};

то порядок инициализации для такой реализации конструктора по умолчанию

inline Account::

Account() : _name( string() ), _balance( 0.0 ), _acct_nmbr( 0 )

{}

будет следующим: _acct_nmbr, _balance, _name. Однако члены, указанные в списке (или в неявно инициализируемом члене-объекте класса), всегда инициализируются раньше, чем производится присваивание членам в теле конструктора. Например, в следующем конструкторе:

inline Account::

Account( const char* name, double bal )

: _name( name ), _balance( bal )

{

_acct_nmbr = get_unique_acct_nmbr();

}

порядок инициализации такой: _balance, _name, _acct_nmbr.

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

class X {

int i;

int j;

public:

// видите проблему?

X( int val )

: j( val ), i( j )

{}

// ...

};

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

// предпочтительная идиома

X::X( int val ) : i( val ) { j = i; }

Упражнение 14.12

Что неверно в следующих определениях конструкторов? Как бы вы исправили обнаруженные ошибки?

(a) Word::Word( char *ps, int count = 1 )

: _ps( new char[strlen(ps)+1] ),

_count( count )

{

if ( ps )

strcpy( _ps, ps );

else {

_ps = 0;

_count = 0;

}

}

(b) class CL1 {

public:

CL1() { c.real(0.0); c.imag(0.0); s = " not set" ; }

// ...

private:

complex c;

string s;

}

(c) class CL2 {

public:

CL2( mapstring,location *pmap, string key )

: _text( key ), _loc( (*pmap)[key] ) {}

// ...

private:

location _loc;

string _text;

};

14.6. Почленная инициализация A

Инициализация одного объекта класса другим объектом того же класса, как, например: