// плохо: не только вызывает деструктор, но и освобождает память
delete ptr;
то, помимо вызова деструктора, еще и возвратим в хип память, чего делать не следовало бы. Вместо этого можно явно вызвать деструктор класса Image:
ptr-~Image();
сохранив отведенную под изображение память для последующего вызова оператора размещения new.
Отметим, что, хотя ptr и arena адресуют одну и ту же область памяти в хипе, применение оператора delete к arena
// деструктор не вызывается
delete arena;
не приводит к вызову деструктора класса Image, так как arena имеет тип char*, а компилятор вызывает деструктор только тогда, когда операндом в delete является указатель на объект класса, имеющего деструктор.
14.3.2. Опасность увеличения размера программы
Встроенный деструктор может стать причиной непредвиденного увеличения размера программы, поскольку он вставляется в каждой точке выхода внутри функции для каждого активного локального объекта. Например, в следующем фрагменте
Account acct( "Tina Lee" );
int swt;
// ...
switch( swt ) {
case 0:
return;
case 1:
// что-то сделать
return;
case 2:
// сделать что-то другое
return;
// и так далее
}
компилятор подставит деструктор перед каждой инструкцией return. Деструктор класса Account невелик, и затраты времени и памяти на его подстановку тоже малы. В противном случае придется либо объявить деструктор невстроенным, либо реорганизовать программу. В примере выше инструкцию return в каждой метке case можно заменить инструкцией break с тем, чтобы у функции была единственная точка выхода:
// переписано для обеспечения единственной точки выхода
switch( swt ) {
case 0:
break;
case 1:
// что-то сделать
break;
case 2:
// сделать что-то другое
break;
// и так далее
}
// единственная точка выхода
return;
Упражнение 14.6
Напишите подходящий деструктор для приведенного набора членов класса, среди которых pstring адресует динамически выделенный массив символов:
class NoName {
public:
~NoName();
// ...
private:
char *pstring;
int ival;
double dval;
};
Упражнение 14.7
Необходим ли деструктор для класса, который вы выбрали в упражнении 14.3? Если нет, объясните почему. В противном случае предложите реализацию.
Упражнение 14.8
Сколько раз вызываются деструкторы в следующем фрагменте:
void mumble( const char *name, fouble balance, char acct_type )
{
Account acct;
if ( ! name )
return;
if ( balance = 99 )
return;
switch( acct_type ) {
case 'z': return;
case 'a':
case 'b': return;
}
// ...
}
14.4. Массивы и векторы объектов
Массив объектов класса определяется точно так же, как массив элементов встроенного типа. Например:
Account table[ 16 ];
определяет массив из 16 объектов Account. Каждый элемент по очереди инициализируется конструктором по умолчанию. Можно и явно передать конструкторам аргументы внутри заключенного в фигурные скобки списка инициализации массива. Строка:
Account pooh_pals[] = { "Piglet", "Eeyore", "Tigger" };
определяет массив из трех элементов, инициализируемых конструкторами:
Account( "Piglet", 0.0 ); // первый элемент (Пятачок)
Account( "Eeyore", 0.0 ); // второй элемент (Иа-Иа)
Account( "Tigger", 0.0 ); // третий элемент (Тигра)
Один аргумент можно задать явно, как в примере выше. Если же необходимо передать несколько аргументов, то придется воспользоваться явным вызовом конструктора:
Account pooh_pals[] = {
Account( "Piglet", 1000.0 ),
Account( "Eeyore", 1000.0 ),
Account( "Tigger", 1000.0 )
};
Чтобы включить в список инициализации массива конструктор по умолчанию, мы употребляем явный вызов с пустым списком параметров:
Account pooh_pals[] = {
Account( "Woozle", 10.0 ), // Бука
Account( "Heffalump", 10.0 ), // Слонопотам
Account();
};
Эквивалентный массив из трех элементов можно объявить и так: