Шрифт:
void h (* char_stack s1(100); char_stack s2 = s1; // неприятность char_stack s3(99); s3 = s2; // неприятность *)
Здесь char_stack::char_stack вызывается дважды: для s1 и для s3. Для s2 он не вызывается, поскольку эта переменная инициализируется присваиванием. Однако деструктор char_stack::~char_stack вызывается трижды: для s1, s2 и s3! Кроме того, по умолчанию действует интерпретация присваивания как побитовое копирование, поэтому в конце h каждый из s1, s2 и s3 будет содержать указатель на вектор символов, размщенный в свободной памяти при создании s1. Не останется никкого указателя на вектор символов, выделенный при создании s3. Таких отклонений можно избежать: см. Главу 6.
5.5.2 Статическая память
Рассмотрим следующее:
table tbl1(100);
void f (* static table tbl2(200); *)
main (*
f; *)
Здесь конструктор table::table, определенный в #5.3.1, будет вызываться дважды: один раз для tbl1 и один раз для tbl2. Деструктор table::~table также будет вызван дважды: для уничтожения tbl1 и tbl2 после выхода из main. Конструторы для глобальных статических объектов в файле выполняются в том порядке, в котором встречаются описания; деструкторы вызываются в обратном порядке. Неопределено, вызывается ли конструктор для локального статического объекта, если фунция, в которой этот объект описан, не вызывается. Если контруктор для локального статического объекта вызывается, то он вызывается после того, как вызваны конструкторы для лексичеки предшествующих ему глобальных статических объектов.
Параметры конструкторов для статических объектов должны быть константными выражениями:
void g(int a) (* static table t(a); // ошибка *)
Традиционно выполнением программы считалось выполнение main. Так никогда не было, даже в C, но только размещение статических объектов класса с конструктором и/или деструктром дают программисту простой и очевидный способ задания тго, что будет выполняться до и/или после вызова main.
Вызов конструкторов и деструкторов для статических обектов играет в С++ чрезвычайно важную роль. Это способ обеспечить надлежащую инициализацию и очистку структур данных в библиотеках. Рассмотрим «stream.h». Откуда берутся cin, cout и cerr? Где они получают инициализацию? И, что самое главное, поскольку потоки вывода имеют внутренние буферы сиволов, как же эти буферы заполняются? Простой и очевидный овет таков, что эта работа осуществляется соответствующими конструкторами и деструкторами до и после выполнения main. Для инициализации и очистки библиотечных средств есть возмоности, альтернативные использованию конструкторов и деструторов. Все они или очень специальные, или очень уродливые.
Если программа завершается с помощью функции exit, то деструкторы для статических объектов будут вызваны, а если она завершается с помощью abort, то не будут. Заметьте, что это подразумевает, что exit не завершает программу мгновено. Вызов exit в деструкторе может привести к бесконечной рекурсии.
Иногда, когда вы разрабатываете библиотеку, необходимо или просто удобно создать тип с конструктором и деструктором, предназначенными только для одного: инициализировать и очитить. Такой тип обычно используется только с одной целью, для размещения статического объекта так, чтобы вызывались контруктор и деструктор.
5.5.3 Свободная память
Рассмотрим:
main (* table* p = new table(100); table* q = new table(200); delete p; delete p; // возможно, ошибка *)
Конструктор table::table будет вызван дважды, как и деструктор table::~table. То, что С++ не дает никаких грантий, что для объекта, созданного с помощью new, когда-либо будет вызван деструктор, ничего не значит. В предыдущей прорамме q не уничтожается, а p уничтожается дважды! Программист может счесть это ошибкой, а может и не счесть, в зависимости от типа p и q. Обычно то, что объект не уничтожается, являеся не ошибкой, а просто лишней тратой памяти. Уничтожение p дважды будет, как правило, серьезной ошибкой. Обычно резултатом применения delete дважды к одному указателю приводит к бесконечному циклу в подпрограмме управления свободной пмятью, но определение языка не задает поведение в таком слчае, и оно зависит от реализации.
Пользователь может определить новую реализацию операций new и delete (см. #3.2.6). Можно также определить способ взимодействия конструктора или деструктора с операциями new и delete (см. #5.5.6)
5.5.4 Объекты класса как члены
Рассмотрим
class classdef (* table members; int no_of_members; // ... classdef(int size); ~classdef; *);
Очевидное намерение состоит в том, что classdef должен содержать таблицу длиной size из членов members, а сложность – в том, как сделать так, чтобы конструктор table::table вызывался с параметром size. Это делается так:
classdef::classdef(int size) : members(size) (* no_of_members = size; // ... *)
Параметры для конструктора члена (здесь это table::table ) помещаются в определение (не в описание) конструктора класса, вмещающего его (здесь это classdef::classdef). Поле этого конструктор члена вызывается перед телом конструктра, задающего его список параметров.
Если есть еще члены, которым нужны списки параметров для конструкторов, их можно задать аналогично. Например:
class classdef (* table members; table friends; int no_of_members; // ... classdef(int size); ~classdef; *);
Список параметров для членов разделяется запятыми (а не двоеточиями), и список инициализаторов для членов может представляться в произвольном порядке:
classdef::classdef(int size)
: friends(size), members(size) (* no_of_members = size; // ... *)
Порядок, в котором вызываются конструкторы, неопределен, поэтому не рекомендуется делать списки параметров с побочными эффектами:
classdef::classdef(int size) : friends(size=size/2), members(size); // дурной стиль (* no_of_members = size; // ... *)