«Если бы мы действительно освобождали память в программе, то программа после попытки освободить уже освобождённую память оказалась бы в нестабильном состоянии и могла аварийно завершиться.»
[Атас!]
Конструктор вызывается один раз и выделяет блок памяти из кучи для хранения в нём имени человека. Копирующий конструктор, создаваемый С++, просто копирует этот адрес в новый объект, без выделения нового блока памяти.
Когда объекты ликвидируются, деструктор для р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( )
Конструирование Достаточно_длинное_имя
Копирование Достаточно_длинное_имя в собственный блок
Деструкция Достаточно_длинное_имя
Деструкция Достаточно_длинное_имя
Возврат из fn( )
Press any key to continue...
_________________
220 стр. Часть 3. Введение в классы
Как видите, теперь указатели на строки в р1 и р2 указывают на разные данные.
Рис. 18.2. Глубокое копирование объекта p1 в р2
►Временные объекты...221
Копии создаются не только тогда, когда объекты передаются в функции по значению. Копии объектов могут создаваться и по другим причинам, например при возврате объекта по значению. Рассмотрим пример.