Конструирование "Достаточно длинное имя"
Деструкция "Достаточно длинное имя"
Деструкция "Уже освобождённая память"
Возврат из fn( )
Press any key to continue...
В этом примере конструктор для Person выделяет память из кучи для хранения в ней имени произвольной длины, что невозможно при использовании массивов. Деструктор возвращает эту память в кучу. Основная программа вызывает функцию fn( ), которая создаёт объект p1, описывающий человека, после чего создаётся копия этого объекта — р2. Оба объекта автоматически уничтожаются при выходе из функции fn( ).
После запуска этой программы вы получите сообщение только от одного конструктора. Это неудивительно, поскольку копия р2 создаётся с помощью предоставляемого С++ конструктора копирования по умолчанию, а он не выводит никаких сообщений. Однако, после того как p1 и р2 выходят из области видимости, вы не получите двух сообщений о ликвидации объектов, как можно было ожидать. Первый конструктор выводит ожидаемое сообщение о деструкции объекта, но второй деструктор сообщает, что память уже была освобождена.
«Если бы мы действительно освобождали память в программе, то программа после попытки освободить уже освобождённую память оказалась бы в нестабильном состоянии и могла аварийно завершиться.»
[Атас!]
Конструктор вызывается один раз и выделяет блок памяти из кучи для хранения в нём имени человека. Копирующий конструктор, создаваемый С++, просто копирует этот адрес в новый объект, без выделения нового блока памяти.
Когда объекты ликвидируются, деструктор для р2 первым получает доступ к этому блоку памяти. Этот деструктор стирает имя и освобождает блок памяти. К тому времени как деструктор p1 получает доступ к этому блоку, память уже очищена, а имя стёрто. Теперь понятно, откуда взялось сообщение об ошибке. Суть проблемы проиллюстрирована на рис. 18.1. Объект p1 копируется в новый объект р2, но не копируются используемые им ресурсы. Таким образом, р1 и р2 указывают на один и тот же ресурс ( в данном случае это блок памяти ). Такое явление называется "мелким" ( shallow ) копированием, поскольку при этом копируются только члены класса как таковые.
«Решение этой проблемы визуально показано на рис. 18.2. В данном случае нужен такой копирующий конструктор, который будет выделять ресурсы для нового объекта. Давайте добавим такой конструктор к классу и посмотрим, как он работает ( здесь приведён только фрагмент программы, полностью находящейся на прилагаемом компакт-диске ).»
[Диск]
class Person
{
public :
Person( char *pN )
{
cout << "Конструирование " << pN << endl ;
pName = new char[ strlen( pN ) + 1 ] ;
if ( pName != 0 )
{
strcpy( pName , pN ) ;
}
}
_________________
219 стр. Глава 18. Копирующий конструктор
/* Копирующий конструктор выделяет новый блок памяти из кучи */
Person( Person& p )
{
cout << "Копирование " << p.pName
<< " в собственный блок" << endl ;
pName = new char[ strlen( p.pName ) + 1 ] ;
if ( pName != 0 )
{
strcpy( pName , p.pName ) ;
}
}
~Person( )
{
cout << "Деструкция " << pName << endl ;
strcpy( pName , "Уже освобождённая память" ) ;
/* delete pName ; */
}
protected :
char *pName ;
} ;
Рис. 18.1. Мелкое копирование объекта p1 в р2
Здесь копирующий конструктор выделяет новый блок памяти для имени, а затем копирует содержимое блока памяти исходного объекта в этот новый блок ( рис. 18.2 ). Такое копирование называется "глубоким" ( deep ), поскольку копирует не только элементы, но и занятые ими ресурсы ( конечно, аналогия, как говорится, притянута за уши, но ничего не поделаешь — не я придумал эти термины ).
Запуск программы с новым копирующим конструктором приведёт к выводу на экран следующих строк:
Вызов fn( )
Конструирование Достаточно_длинное_имя