C++
Шрифт:
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. Обычно то, что объект
Пользователь может определить новую реализацию операций 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; // ... *)
Если конструктору для члена не нужно ни одного парамера, то никакого списка параметров задавать не надо. Например, поскольку table::table был определен с параметром по умолчнию 15, следующая запись является правильной:
classdef::classdef(int size) : members(size) (* no_of_members = size; // ... *)
и размер size таблицы friends будет равен 15.
Когда объект класса, содержащий объект класса, (напрмер, classdef) уничтожается, первым выполняется тело собтвенного деструктора объекта, а затем выполняются деструкторы членов.
Рассмотрим традиционную альтернативу тому, чтобы иметь объекты класса как члены, – иметь члены указатели и инициалзировать их в конструкторе:
class classdef (* table* members; table* friends; int no_of_members; // ... classdef(int size); ~classdef; *);
classdef::classdef(int size) (* members = new table(size); friends = new table; // размер таблицы по умолчанию no_of_members = size; // ... *)
Так как таблицы создавались с помощью new, они должны уничтожаться с помощью delete:
classdef::~classdef (* // ... delete members; delete friends; *)
Раздельно создаваемые объекты вроде этих могут оказаться полезными, но учтите, что members и friends указывают на одельные объекты, что требует для каждого из них действие по выделению памяти и ее освобождению. Кроме того, указатель плюс объект в свободной памяти занимают больше места, чем объект член.
5.5.5 Вектора объектов класса
Чтобы описать вектор объектов класса, имеющего конструтор, этот класс должен иметь конструктор, который может вызваться без списка параметров. Нельзя использовать даже парметры по умолчанию. Например:
table tblvec[10];
будет ошибкой, так как для table::table требуется целый параметр. Нет способа задать параметры конструктора в описании вектора. Чтобы можно было описывать вектор таблиц table, можно модифицировать описание table (#5.3.1), напрмер, так:
class table (* // ... void init(int sz); // как старый конструктор public: table(int sz) // как раньше, но без по умолчанию (* init(sz); *) table // по умолчанию (* init(15); *) *)
Когда вектор уничтожается, деструктор должен вызываться для каждого элемента этого вектора. Для векторов, которые не были размещены с помощью new, это делается неявно. Однако для векторов в свободной памяти это не может быть сделано неявно, поскольку компилятор не может отличить указатель на один обект от указателя на первый элемент вектора объектов. Например:
void f (* table* t1 = new table; table* t2 = new table[10]; delete t1; // одна таблица delete t2; // неприятность: 10 таблиц *)
В этом случае длину вектора должен задавать программист:
void g(int sz) (* table* t1 = new table; table* t2 = new table[sz]; delete t1; delete[] t2; *)
Но почему же компилятор не может найти число элементов вектора из объема выделенной памяти? Потому, что распределтель свободной памяти не является частью языка и может быть задан программистом.
5.5.6 Небольшие объекты
Когда вы используете много небольших объектов, размещамых в свободной памяти, то вы можете обнаружить, что ваша
программа тратит много времени выделяя и освобождая память под эти объекты. Первое решение – это обеспечить более хорший распределитель памяти общего назначения, второе для раработчика классов состоит в том, чтобы взять под контроль уравление свободной памятью для объектов некоторого класса с помощью подходящих конструкторов и деструкторов.