Рассмотрим пример:
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 и $$6.7). Кроме того, можно установить
взаимодействие конструктора или деструктора с операциями new и
delete (см. $$5.5.6 и $$6.7.2). Размещение массивов в свободной
памяти обсуждается в $$5.5.5.
5.5.4 Объекты класса как члены
Рассмотрим пример:
class classdef {
table members;
int no_of_members;
// ...
classdef(int size);
~classdef();
};
Цель этого определения, очевидно, в том, чтобы classdef содержал
член, являющийся таблицей размером size, но есть сложность: надо
обеспечить вызов конструктора 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)
{
// ...
}
Конструкторы вызываются в том порядке, в котором они заданы в
описании класса.
Подобные описания конструкторов существенны для типов,
инициализация и присваивание которых отличны друг от друга, иными
словами, для объектов, являющихся членами класса с конструктором,
для постоянных членов или для членов типа ссылки. Однако, как
показывает член no_of_members из приведенного примера, такие
описания конструкторов можно использовать для членов любого
типа.
Если конструктору члена не требуется параметров, то и не нужно
задавать никаких списков параметров. Так, поскольку конструктор
table::table() был определен со стандартным значением параметра,
равным 15, достаточно такого определения:
classdef::classdef(int size)
: members(size), no_of_members(size)
{
// ...
}
Тогда размер таблицы friends будет равен 15.
Если уничтожается объект класса, который сам содержит объекты
класса (например, classdef), то вначале выполняется тело
деструктора объемлющего класса, а затем деструкторы членов в порядке,
обратном их описанию.
Рассмотрим вместо вхождения объектов класса в качестве членов
традиционное альтернативное ему решение: иметь в классе указатели
на члены и инициализировать члены в конструкторе:
class classdef {
table* members;
table* friends;
int no_of_members;
// ...
};
classdef::classdef(int size)
{
members = new table(size);
friends = new table; // используется стандартный
// размер table
no_of_members = size;
// ...
}
Поскольку таблицы создавались с помощью операции new, они должны
уничтожаться операцией delete:
classdef::~classdef()
{
// ...
delete members;
delete friends;
}
Такие отдельно создаваемые объекты могут оказаться полезными, но
учтите, что members и friends указывают на независимые от них
объекты, каждый из которых надо явно размещать и удалять. Кроме
того, указатель и объект в свободной памяти суммарно занимают
больше места, чем объект-член.
5.5.5 Массивы объектов класса
Чтобы можно было описать массив объектов класса с конструктором,
этот класс должен иметь стандартный конструктор, т.е. конструктор,
вызываемый без параметров. Например, в соответствии с определением
table tbl[10];
будет создан массив из 10 таблиц, каждая из которых инициализируется
вызовом table::table(15), поскольку вызов table::table() будет
происходить с фактическим параметром 15.
В описании массива объектов не предусмотрено возможности указать
параметры для конструктора. Если члены массива обязательно надо
инициализировать разными значениями, то начинаются трюки с
глобальными или статическими членами.
Когда уничтожается массив, деструктор должен вызываться для
каждого элемента массива. Для массивов, которые размещаются не
с помощью new, это делается неявно. Однако для размещенных в свободной
памяти массивов неявно вызывать деструктор нельзя, поскольку транслятор
не отличит указатель на отдельный объект массива от указателя на начало
массива, например:
void f()
{
table* t1 = new table;
table* t2 = new table[10];
delete t1; // удаляется одна таблица
delete t2; // неприятность:
// на самом деле удаляется 10 таблиц
}
В данном случае программист должен указать, что t2 - указатель
на массив:
void g(int sz)
{
table* t1 = new table;
table* t2 = new table[sz];
delete t1;
delete[] t2;
}
Функция размещения хранит число элементов для каждого размещаемого
массива. Требование использовать для удаления массивов только операцию
delete[] освобождает функцию размещения от обязанности хранить счетчики
числа элементов для каждого массива. Исполнение такой обязанности в
реализациях С++ вызывало бы существенные потери времени и памяти
и нарушило совместимость с С.
Достарыңызбен бөлісу: |