14.2. Конструктор класса

14.2. Конструктор класса

Среди других функций-членов конструктор выделяется тем, что его имя совпадает с именем класса. Для объявления конструктора по умолчанию мы пишем2:

class Account {

public:

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

Account();

// ...

private:

char *_name;

unsigned int _acct_nmbr;

double _balance;

};

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

// ошибки: у конструктора не может быть типа возвращаемого значения

void Account::Account() { ... }

Account* Account::Account( const char *pc ) { ... }

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

Откуда мы знаем, сколько и каких конструкторов определить? Как минимум, необходимо присвоить начальное значение каждому члену, который в этом нуждается. Например, номер счета либо задается явно, либо генерируется автоматически таким образом, чтобы гарантировать его уникальность. Предположим, что он будет создаваться автоматически. Тогда мы должны разрешить инициализировать оставшиеся два члена _name и _balance:

Account( const char *name, double open_balance );

Объект класса Account, инициализируемый конструктором, можно объявить следующим образом:

Account newAcct( "Mikey Matz", 0 );

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

Account( const char *name );

Другой способ - включить в конструктор с двумя параметрами значение по умолчанию, равное нулю:

Account( const char *name, double open_balance = 0.0 );

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

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

class Account {

public:

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

Account();

// имена параметров в объявлении указывать необязательно

Account( const char*, double=0.0 );

const char* name() { return name; }

// ...

private:

// ...

};

Ниже приведены два примера правильного определения объекта класса Account, где конструктору передается один или два аргумента:

int main()

{

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

// с двумя параметрами

Account acct( "Ethan Stern" );

Account *pact = new Account( "Michael Lieberman", 5000 );

if ( strcmp( acct.name(), pact-name() ))

// ...

}

C++ требует, чтобы конструктор применялся к определенному объекту до его первого использования. Это означает, что как для acct, так и для объекта, на который указывает pact, конструктор будет вызван перед проверкой в инструкции if.

Компилятор перестраивает нашу программу, вставляя вызовы конструкторов. Вот как, по всей вероятности, будет модифицировано определение acct внутри main():

// псевдокод на C++,

// иллюстрирующий внутреннюю вставку конструктора

int main()

{

Account acct;

acct.Account::Account("Ethan Stern", 0.0);

// ...

}

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

Обработка оператора new несколько сложнее. Конструктор вызывается только тогда, когда он успешно выделил память. Модификация определения pact в несколько упрощенном виде выглядит так:

// псевдокод на C++,

// иллюстрирующий внутреннюю вставку конструктора при обработке new

int main()

{

// ...

Account *pact;

try {

pact = _new( sizeof( Account ));

pact-Acct.Account::Account(

"Michael Liebarman", 5000.0);

}

catch( std::bad_alloc ) {

// оператор new закончился неудачей:

// конструктор не вызывается

}

// ...

}

Существует три в общем случае эквивалентных формы задания аргументов конструктора:

// в общем случае эти формы эквивалентны

Account acct1( "Anna Press" );

Account acct2 = Account( "Anna Press" );

Account acct3 = "Anna Press";

Форма acct3 может использоваться только при задании единственного аргумента. Если аргументов два или более, мы рекомендуем пользоваться формой acct1, хотя допустима и acct2.

// рекомендуемая форма вызова конструктора

Account acct1( "Anna Press" );

Новички часто допускают ошибку при объявлении объекта, инициализированного конструктором по умолчанию:

// увы! работает не так, как ожидалось

Account newAccount();

Эта инструкция компилируется без ошибок. Однако при попытке использовать объект в таком контексте:

// ошибка компиляции ...

if ( ! newAccount.name() ) ...

компилятор не сможет применить к функции нотацию доступа к членам класса. Определение

// определяет функцию newAccount,

// а не объект класса

Account newAccount();

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

// правильно: определяется объект класса ...

Account newAccount;

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

class Account {

public:

// имена параметров в объявлении указывать необязательно

Account( const char*, double=0.0 );

const char* name() { return name; }

// ...

private:

// ...

};

Теперь при объявлении каждого объекта Account в конструкторе обязательно надо указать как минимум аргумент типа C-строки, но это скорее всего бессмысленно. Почему? Контейнерные классы (например, vector) требуют, чтобы для класса помещаемых в них элементов был либо задан конструктор по умолчанию, либо вообще никаких конструкторов. Аналогичная ситуация имеет место при выделении динамического массива объектов класса. Так, следующая инструкция вызвала бы ошибку компиляции для новой версии Account:

// ошибка: требуется конструктор по умолчанию для класса Account

Account *pact = new Account[ new_client_cnt ];

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

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

// конструктор по умолчанию для класса Account

inline Account::

Account() {

_name = 0;

_balance = 0.0;

_acct_nmbr = 0;

}

Однако в функции-члены класса Account придется включить проверку целостности объекта перед его использованием.

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

// конструктор по умолчанию класса Account с использованием

// списка инициализации членов

inline Account::

Account()

: _name(0),

_balance( 0.0 ), _acct_nmbr( 0 )

{}

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

inline Account::

Account( const char* name, double opening_bal )

: _balance( opening_bal )

{

_name = new char[ strlen(name)+1 ];

strcpy( _name, name );

_acct_nmbr = get_unique_acct_nmbr();

}

get_unique_acct_nmbr() - это не являющаяся открытой функция-член, которая возвращает гарантированно не использованный ранее номер счета.

Конструктор нельзя объявлять с ключевыми словами const или volatile (см. раздел 13.3.5), поэтому приведенные записи неверны:

class Account {

public:

Account() const; // ошибка

Account() volatile; // ошибка

// ...

};

Это не означает, что объекты класса с такими спецификаторами запрещено инициализировать конструктором. Просто к объекту применяется подходящий конструктор, причем без учета спецификаторов в объявлении объекта. Константность объекта класса устанавливается после того, как работа по его инициализации завершена, и пропадает в момент вызова деструктора. Таким образом, объект класса со спецификатором const считается константным с момента завершения работы конструктора до момента запуска деструктора. То же самое относится и к спецификатору volatile.

Рассмотрим следующий фрагмент программы:

// в каком-то заголовочном файле

extern void print( const Account &acct );

// ...

int main()

{

// преобразует строку "oops" в объект класса Account

// с помощью конструктора Account::Account( "oops", 0.0 )

print( "oops" );

// ...

}

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

Непреднамеренные неявные преобразования классов, например трансформация "oops" в объект класса Account, оказались источником трудно обнаруживаемых ошибок. Поэтому в стандарт C++ было добавлено ключевое слово explicit, говорящее компилятору, что такие преобразования не нужны:

class Account {

public:

explicit Account( const char*, double=0.0 );

};

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

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

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

// все это конструкторы по умолчанию

Account::Account() { ... }

iStack::iStack( int size = 0 ) { ... }

Complex::Complex(double re=0.0, double im=0.0) { ... }

Когда мы пишем:

int main()

{

Account acct;

// ...

}

* то компилятор сначала проверяет, определен ли для класса Account конструктор по умолчанию. Возникает одна из следующих ситуаций: Такой конструктор определен. Тогда он применяется к acct.

* Конструктор определен, но не является открытым. В данном случае определение acct помечается компилятором как ошибка: у функции main() нет прав доступа.

* Конструктор по умолчанию не определен, но есть один или несколько конструкторов, требующих задания аргументов. Определение acct помечается как ошибка: слишком мало аргументов у конструктора.

* Нет ни конструктора по умолчанию, ни какого-либо другого. Определение считается корректным, acct не инициализируется, конструктор не вызывается.

Пункты 1 и 3 должны быть уже достаточно понятны (если это не так, перечитайте данную главу) Посмотрим более внимательно на пункты 2 и 4.

Допустим, что все члены класса Account объявлены открытыми и не объявлено никакого конструктора:

class Account {

public:

char *_name;

unsigned int _acct_nmbr;

double _balance;

};

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

// статический класс хранения

// вся ассоциированная с объектом память обнуляется

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 практически невозможно.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Софтерра: Вычислительный конструктор

Из книги Журнал «Компьютерра» № 9 от 7 марта 2006 года автора Журнал «Компьютерра»

Софтерра: Вычислительный конструктор Автор: Сергей Гладких sergeyxtr@gmail.comЧеловечество штурмует (или точнее будет сказать — осаждает?) неприступную крепость математики вот уже несколько тысячелетий. Раньше в этом бою мы могли полагаться только на свои собственные силы, на


Дополнительные инструменты окна Titler (Конструктор титров)

Из книги Видеосамоучитель монтажа домашнего видео в Adobe Premiere Pro CS3 автора Днепров Александр Г

Дополнительные инструменты окна Titler (Конструктор титров) Добавим еще два статичных титра в фильм, рассмотрев на примере их создания некоторые дополнительные инструменты окна Titler (Конструктор титров).1. Установите курсор текущей позиции редактирования на клип Image1.jpg.2.


Конструктор, заданный по умолчанию

Из книги Язык программирования С# 2005 и платформа .NET 2.0. [3-е издание] автора Троелсен Эндрю

Конструктор, заданный по умолчанию Заданный по умолчанию конструктор агента "жестко" определяет значение URL удаленного Web-сервиса и запоминает это значение в наследуемом свойстве Url.public CalculatorWebService() { this.Url = "http://localhost/CalcServicе/Service.asmx";}Очевидным недостатком такого подхода


Конструктор узоров

Из книги Photoshop. Мультимедийный курс автора Мединов Олег

Конструктор узоров Фильтр Конструктор узоров позволяет создать узор из любого фрагмента изображения. При выборе команды меню Фильтр ? Конструктор узоров появляется окно фильтра (рис. 11.51). Рис. 11.51. Окно фильтра Конструктор узоровФильтр содержит множество настроек, с


Работа с диаграммами на вкладках Конструктор и Макет

Из книги Excel. Мультимедийный курс автора Мединов Олег

Работа с диаграммами на вкладках Конструктор и Макет Несколько изменим вид круговой диаграммы.• Выделите диаграмму и перейдите на вкладку Конструктор.• Раскройте галерею Макеты диаграмм и выберите Макет 1.Обратите внимание, что легенда справа от диаграммы исчезла. В


Конструктор класса

Из книги Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NT автора Фролов Александр Вячеславович

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


Конструктор

Из книги Как сделать свой сайт и заработать на нем. Практическое пособие для начинающих по заработку в Интернете автора Мухутдинов Евгений


Главный конструктор «Уралов»

Из книги Информационные технологии в СССР. Создатели советской вычислительной техники автора Ревич Юрий Всеволодович

Главный конструктор «Уралов» После завершения работ по «Стреле» он с удвоенной энергией берется за создание машины «Урал-1» (той самой, что на много лет стала потом «рабочей лошадкой» во многих ВЦ страны) с дальним прицелом создать семейство машин, начиная от ЭВМ малой


Конструктор ЭВМ

Из книги C++ для начинающих автора Липпман Стенли

Конструктор ЭВМ Будучи руководителем Вычислительного центра АН УССР, а затем директором Института кибернетики, костяком которых стал коллектив, работавший над первыми отечественными ЭВМ, В. М. Глушков просто не мог не втянуться в область создания средств вычислительной


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

Из книги Архитекторы компьютерного мира автора Частиков Аркадий

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


14.2.3. Копирующий конструктор

Из книги автора

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


15.9.2. Конструктор как конвертер

Из книги автора

15.9.2. Конструктор как конвертер Набор конструкторов класса, принимающих единственный параметр, например, SmallInt(int) класса SmallInt, определяет множество неявных преобразований в значения типа SmallInt. Так, конструктор SmallInt(int) преобразует значения типа int в значения типа SmallInt.extern


17.4.1. Конструктор базового класса

Из книги автора

17.4.1. Конструктор базового класса В нашем базовом классе объявлено два нестатических члена: _solution и _loc:class Query {public:// ...protected:setshort *_solution;vectorlocation _loc;// ...};Конструктор Query по умолчанию должен явно инициализировать только член _solution. Для инициализации _loc автоматически вызывается


17.4.2. Конструктор производного класса

Из книги автора

17.4.2. Конструктор производного класса В классе NameQuery также определены два конструктора. Они объявлены открытыми, поскольку ожидается, что в приложении будут создаваться объекты этого класса:class NameQuery : public Query {public:explicit NameQuery( const string& );NameQuery( const string&, const vector* );// ...protected://