// вся ассоциированная с объектом память обнуляется
Account global_scope_acct;
static Account file_scope_acct;
Account foo()
{
static Account local_static_acct;
// ...
}
Однако объекты, определенные локально или распределенные динамически, в начальный момент будут содержать случайный набор битов, оставшихся в стеке программы:
// локальные и распределенные из хипа объекты не инициализированы
// до момента явной инициализации или присваивания
Account bar()
{
Account local_acct;
Account *heap_acct = new Account;
// ...
}
Новички часто полагают, что компилятор автоматически генерирует конструктор, если он не задан, и применяет его для инициализации членов класса. Для Account в том виде, в каком мы его определили, это неверно. Никакой конструктор не генерируется и не вызывается. Для более сложных классов, имеющих члены, которые сами являются классами, или использующих наследование, это отчасти справедливо: конструктор по умолчанию может быть сгенерирован, но и он не присваивает начальных значений членам встроенных или составных типов, таким, как указатели или массивы.
Если мы хотим, чтобы подобные члены инициализировались, то должны сами позаботиться об этом, предоставив один или несколько конструкторов. В противном случае отличить корректное значение члена такого типа от неинициализированного, если объект создан локально или распределен из хипа,3 практически невозможно.
14.2.2. Ограничение прав на создание объекта
Доступность конструктора определяется тем, в какой секции класса он объявлен. Мы можем ограничить или явно запретить некоторые формы создания объектов, если поместим соответствующий конструктор в неоткрытую секцию. В примере ниже конструктор по умолчанию класса Account объявлен закрытым, а с двумя параметрами - открытым:
class Account {
friend class vector;
public:
explicit Account( const char*, double = 0.0 );
// ...
private:
Account();
// ...
};
Обычная программа сможет теперь определять объекты класса Account, лишь указав как имя владельца счета, так и начальный баланс. Однако функции-члены Account и дружественный ему класс vector могут создавать объекты, пользуясь любым конструктором.
* Конструкторы, не являющиеся открытыми, в реальных программах C++ чаще всего используются для: предотвращения копирования одного объекта в другой объект того же класса (эта проблема рассматривается в следующем подразделе);
* указания на то, что конструктор должен вызываться только в случае, когда данный класс выступает в роли базового в иерархии наследования, а не для создания объектов, которыми программа может манипулировать напрямую (см. обсуждение наследования и объектно-ориентированного программирования в главе 17).
14.2.3. Копирующий конструктор
Инициализация объекта другим объектом того же класса называется почленной инициализацией по умолчанию. Копирование одного объекта в другой выполняется путем последовательного копирования каждого нестатического члена. Проектировщик класса может изменить такое поведение, предоставив специальный копирующий конструктор. Если он определен, то вызывается всякий раз, когда один объект инициализируется другим объектом того же класса.
Часто почленная инициализация не обеспечивает корректного поведения класса. Поэтому мы явно определяем копирующий конструктор. В нашем классе Account это необходимо, иначе два объекта будут иметь одинаковые номера счетов, что запрещено спецификацией класса.
Копирующий конструктор принимает в качестве формального параметра ссылку на объект класса (традиционно объявляемую со спецификатором const). Вот его реализация:
inline Account::
Account( const Account &rhs )
: _balance( rhs._balance )
{
_name = new char[ strlen(rhs._name) + 1 ];
strcpy( _name, rhs._name );
// копировать rhs._acct_nmbr нельзя
_acct_nmbr = get_unique_acct_nmbr();
}
Когда мы пишем:
Account acct2( acct1 );