Выбрать главу

«Если бы мы действительно освобождали память в программе, то программа после попытки освободить уже освобождённую память оказалась бы в нестабильном состоянии и могла аварийно завершиться.»

[Атас!]

Конструктор вызывается один раз и выделяет блок памяти из кучи для хранения в нём имени человека. Копирующий конструктор, создаваемый С++, просто копирует этот адрес в новый объект, без выделения нового блока памяти.

Когда объекты ликвидируются, деструктор для р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

Копии создаются не только тогда, когда объекты передаются в функции по значению. Копии объектов могут создаваться и по другим причинам, например при возврате объекта по значению. Рассмотрим пример.