Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
274
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

460

Часть II # Объектно-ориентированное орогро.-

-АВ на C4-I-

Когда исчезает второй объект, вызывается деструктор. Обратите внимание., м^ "снова вызывается". Ранее деструктор вызывался для другого объекта (формаль­ ного параметра), и он уже уничтожен. Теперь он используется для второго объекта (фактического параметра) и пытается освободить тот же сегмент динамически распределяемой памяти. В C + + такое действие приведет к ошибке. Поведение программы будет не определено, т. е. она будет делать то, что заблагорассудится.

Решение проблем целостности

Есть ряд способов, которыми можно воспользоваться, чтобы избежать не­ приятностей при передаче как значений параметров-объектов с динамически распределяемой памятью.

Один из них заключается в том, чтобы устранить деструктор, возвраш^ающий динамически распределяемую память системе. Это решение нельзя считать пра­ вильным. Им можно воспользоваться в случае, когда программа аварийно за­ вершает работу и нужно выполнить ее для отладки. Отключение деструктора позволит выполнить программу до конца.

Еш.е один способ состоит в использовании внутри объектов массивов фиксиро­ ванного размера, а не динамически распределяемой памяти. Такое решение может подойти при частом изменении размера массива. Оно особенно хорошо для про­ грамм, работаюш,их с относительно большим числом объектов, и когда отсечение данных, не помеш,ающихся в область фиксированного размера, считается прием­ лемым с точки зрения целостности приложения.

Лучший способ для передачи параметра — это передача по ссылке, а не по значению. В этом случае устраняются проблемы, создаваемые копированием объ­ ектов, ускоряется выполнение программы, исключается необходимость создания

иуничтожения временных объектов, вызова конструкторов и деструкторов.

Ксожалению, данное решение не универсально. Есть случаи, когда копирова­ ние одного объекта в другой не имеет отношения к передаче параметров, и такое решение неприменимо (например, один объект класса инициализируется другим объектом того же класса). Рассмотрим следуюш^ий фрагмент программы, где па­ раметр передается функции operator+=() по ссылке.

String

иС'Это тест."), v("Bce

нормально.");

// результат OK

cout

«

''

u =

«

U. showO

«

endl;

cout

«

''

V =

«

V.ShowO

«

endl;

// результат OK

u += v;

 

 

«

u. ShowO

«

endl;

// u.operator+=(v); noссылке

cout

«

''

u =

// результат OK

cout

«

''

V =

«

V.ShowO

«

endl;

// OK: передача noссылке

v.modify("Давайте надеяться на лучшее.

// нет порчи содержимого памяти

String t = v;

 

t.ShowO

«

endl;

// инициализация объекта

cout «

" t = " «

// ОК: корректный результат

t.modifyC'Bce нормально.");

«

endl;

// изменяются t и v

cout «

" t = " «

t.ShowO

// OK: результат

cout «

" V = " «

V.ShowO

«

endl;

// V также изменен

U= Это тест.

V= Все нормально.

U= Это тест. Все нормально.

V= Все нормально.

t = Давайте надеяться

t = Все нормально. V = Все нормально.

Рис. 11.12.

Ожидаемый результат выполнения приведенного фрагмента кода

Этот фрагмент создает два объекта String (и и v), инициализи­ рует их с помощью конструктора преобразования и конкатени­ рует. Так как объект-аргумент v передается функции operateг+=() по ссылке, порчи содержимого памяти здесь не будет. Объект v поддерживает собственную память. При модификации объекта v изменяется только этот объект, а не объект и. Далее создается еще один объект типа String, объект t. Ему присваивается те­ кущее состояние объекта v. При изменении содержимого объек­ та t объект V останется без изменений. На рис. 11.12 показаны предполагаемые результаты выполнения данного фрагмента программы.

Глава 11 • Конструкторы и деструкторы: потенциальные пробле1^1ы

ТбГ!

Рис. 11.13. Результат

выполнения программы

из листинга

НА

Но в реальной жизни бывают исключения. Листинг 11.4 показывает програм­ мный код для класса String (с параметром, передаваемым перегруженной опера­ торной функции operator+=() по ссылке), и клиента, реализуюи;его приведенный выше фрагмент. Фрагмент программы изменен таким образом, чтобы объект t со­ здавался во вложенном цикле. Когда вложенный цикл завершает работу и объект t исчезает, можно проверить состояние объекта и его целостность. На рис. 11.13 показаны результаты выполнения программы из листинга 11.4.

Листинг 11.4. Инициализация одного объекта с помощью данных другого объекта

#inclucle <iostream> using namespace std;

class String { • char *str;

int len; public:

String (int length=0); String(const char*); "String 0 ;

void operator += (const String); void modify(const char*);

const char* showO const;

} ;

String::String(int length) { len = length;

str = new char[len+1];

//динамически распределяемый символьный массив

//конструктор преобразования по умолчанию

//конструктор преобразования

//освобождение памяти

//конкатенация с другим объектом

//изменение содержимого массива

//возврат указателя массива

if (str==NULL) exit(1);

// пустая строка нулевой длины - ОК

str[0] = 0; }

462 Чость П • Объектно-ориентированное прогроммировоние но С+ч-

String::String(const char* s)

// определение длины входного текста

{

len = strlen(s);

 

str = new char[len+1];

// выделение достаточной памяти вдинамической области

 

if (str==NULL) exitd);

// проверка науспех

 

 

strcpy(str,s); }

// копировать входящий текст в память

String::~String()

// возврат памяти вдинамической области (не указателя)

{ delete str; }

voidString::operator += (const String& s)

// передача по значению

{

len = strlen(str) + strlen(s.str);

// защита от переполнения

 

 

char* р = new char[len + 1];

// выделение достаточного объема памяти

 

if (p==NULL) exit(1);

// проверка на успех

результата

 

strcpy(p,str);

// копирование первой части

 

strcat (p.s.str);

// добавление второй части

результата

 

delete str;

// важный шаг

 

 

str = p; }

// теперь р может исчезнуть

 

const char* String: :show() const

// защита данных от изменений

{

return str; }

 

 

 

void String::modify(const char a[])

{ strncpy(str,a,len-1); str[len-1] = 0; }

//здесь нет управления памятью

//защита от переполнения

//правильное завершение строки

int mainO

endl «

endl;

 

 

 

 

{ cout «

 

 

 

 

String иС'Проверка");

 

 

 

 

String v("HH4ero плохого неслучится");

// результат OK

cout

«

u

«

u.showO

«

endl;

cout

«

'' V

«

v.showO

«

endl;

// результат OK

u += v;

 

«

U.showO

«

endl;

//u.operator+=(s);

cout

«

'' u

/ /

результат OK

cout

«

'' V

«

V.showO

«

endl;

/ /

результат не OK

v.modify("Дaвaйтe

надеяться на лучшее."

/ /

порча содержимого памяти

{ String t = v;

 

t.showO

« endl;

/ /

инициализация

cout « " t = " «

/ /

OK, корректный результат

t.modify("Ничего

плохого неслучится,

/ /

изменяется t и v

cout « " V = " «

V.showO

« endl; }

/ /

V также изменился

cout «

" V = " «

V.showO

«

endl;

/ /

t больше нет, v теряет память

return 0;

При создании строкового объекта t типа String (для него отводится память в стеке, так как t — локальная автоматическая переменная) выделенной памяти достаточно для символьного указателя и целого. Вызывается конструктор. В кли­ енте можно видеть символ присваивания, но это инициализация. Важно знать, какой вызывается конструктор после создания объекта. Ответ зависит отданных, подставляемых клиентом при создании объекта. В листинге 11.4 функция main() подставляет один фактический аргумент — существуюидий объект v. Следователь­ но, это должен быть конструктор с одним параметром того же типа, в данном случае конструктор класса String.

Каково же имя данного конструктора с одним параметром? Как говорилось в главе 9, это конструктор копирования, поскольку он копирует данные из одного объекта в другой. Между тем класс String не имеет конструктора копирования. Это не означает, что попытка вызова отсутствующеего конструктора даст синтак­ сическую ошибку. Компилятор генерирует вызов конструктора копирования,

Глава 11 • Конструкторы и деструкторы: потенциальные проблемы

463

подставляемого системой. Конструктор копирует поля аргумента-объекта в поля создаваемого объекта. Для класса String подставляемый системой конструктор выглядит следующим образом:

String::String(const String& s)

/ /

конструктор, подставляемый системой

{ Ien = s.len;

/ /

копирование длины текстового объекта

str = S.Str; }

/ /

копирование указателя текстового объекта

На рис. 11.14 показана работа конструктора. При создании объекта t типа String его полю Ien присваивается значение 9, а поле str устанавливается на ту же область динамически распределяемой памяти, на которую указывает поле str объекта v.

А)

и

В)

str

Hi there!\0

ien

 

Str

Ien

W

1 ^

Client code; String t = v;

str

Ien

К

1 ^

Client code; t.modlfy(-Hello");

Рис. 11.14. Диаграмма памят,и для инициализации

одного объекта String

данными другого объектна

 

Объекты t и V ссылаются на один сегмент динамически распределяемой па­

мяти. Ранее он был выделен для объекта

v, но теперь на него ссылается еще

и объект t. Каждый объект считает, что эта область памяти принадлежит только ему. Еще хуже, что здесь имеет место передача по значению. При передаче по значению фактический аргумент существует в области действия клиента, а фор­ мальный параметр — в области сервера. При выполнении доступен только один объект. Оба объекта существуют в области действия клиента. Обращение к ним и модификация происходят в одной и той же области.

Объекты используют одну и ту же область динамически распределяемой памя­ ти. Для клиента они являются синонимами. Если в клиенте изменяется объект t, меняется и объект v.

Если подумать, то странным это кажется только с точки зрения общепринятого подхода в программировании. При пояснении классов уже рассказывалось, что у студентов часто возникают трудности с простым исходным кодом с целыми числами:

int V = 10; int

t

t = 20;

/ / чему теперь равно v?

Большинство

программистов

считают, что после изменения t переменная v

остается прежней, так как t и v занимают разные области памяти. Другие пола­ гают, что переменные v и t — одно и то же, так что не удивительно, когда при изменении t меняется и значение v.

Если переменные являются синонимами, то изменение одной из них отражает­ ся на другой. Помните, что часто одна переменная является обычной переменной, а другая — ссылкой.

int V = 10; int& t = v; t = 20; / / чему теперь равно v?

В данном примере трудно разобраться. В записи утверждается, что две пере­ менные V и t одно и то же. Не удивительно, что v изменяется после изменения t. Теперь V равно 20. Это должны усвоить программисты, применяющие C+ + .

464

Часть il * Объектно-opi

:^ирование

Семантика копирования и семантика значений

Суш,ествуют два обндих подхода в программировании, соответствующих двум разным концепциям вычислительной техники — семантика значения и семан­ тика ссылок. (Под семантикой здесь понимается смысл копирования данных.)

В самом распространенном подходе в программировании используется семан­ тика значений. Каждый вычислительный объект (например, переменная встроен­ ного типа или объект определяемого программистом типа) имеет собственную отдельную область памяти. Если один вычислительный объект приравнять к дру­ гому, повторится битовая последовательность одного объекта в памяти другого объекта. В C + + семантика значений используется как для встроенных перемен­ ных, так и для объектов определяемого программистом типа.

i nt V = 10; int t = v; t = 20;

/ / семантика значений, v = 10

Такая интуиция более распространена по следующей причине: с ее точки зрения, когда объекты имеют одно значение, они используют две разные битовые последовательности, и изменение одного объекта не влияет на последователь­ ность битов, уже существующую в другом объекте.

В другой, менее распространенной интуиции программирования используется семантика ссылок. Когда вычислительному объекту присваивается значение, он получает ссылку (или указатель) на это значение. Приравнивание вычислительных объектов означает присваивание их ссылкам одной и той же области памяти. Когда изменяется массив символов, на который указывает один из этих объектов, меняется другой объект, так как оба объекта ссылаются на одну область. В C+ + такая семантика ссылок используется для указателей и ссылок, при передаче па­ раметров по ссылке или по указателю, для массивов и связанных структур данных с указателями.

int V = 10; int& t = v; t = 20;

/ / согласно семантике ссылок, v = 20

Семантика ссылок распространена меньнле. Она используется в основном из соображений производительности (например, исключает копирование объектов при передаче параметров). Иногда она действует неявно, как в данном примере. Что касается C+ + , то программист всегда должен помнить о разнице между семантикой значений и семантикой ссылок.

Вы узнали еще не обо всех неприятностях с программой из листинга 11.4. Когда при ее выполнении достигается закрывающая фигурная скобка вложенного цикла, объект t должен исчезнуть, так как он определен в этом вложенном цикле. Объект V определен во внешней области действия функции main() и должен быть доступен для дальнейшего использования. В листинге 11.4 показана попытка вы­ вода значения v в конце функции main(). Обратите внимание, что этот оператор отделен от предшествующего оператора вывода только закрывающей фигурной скобкой вложенной области действия. На первый взгляд, между двумя оператора­ ми в клиенте не происходит никаких событий. Следовательно, они должны давать один и тот же результат. Но результат разный. В этом случае рекомендуется выра­ ботать вам собственный подход, который поможет читать подобные фрагменты исходного кода.

Первый оператор дает вполне нормальный результат (см. рис. 11.14). Это не совсем то, что можно было бы ожидать, но, по крайней мере, он есть. Второй результат — просто "мусор". Что произошло между двумя операторами? Когда достигается закрывающая фигурная скобка области действия, для локального объекта t в этой вложенной области вызывается деструктор класса String. Как видно из листинга 11.4 и из рис. 11.14, данный деструктор освобождает динами­ чески распределяемую память, принадлежащую объекту Ь, но система этого не помнит. Она запоминает лишь то, что память, на которую ссылается str, следует освободить согласно деструктору класса String. У объекта v уже нет динамиче­ ской памяти, но никто об этом не знает. Формально он находится в области дей­ ствия. Но это только на первый взгляд.

Глава 11 * Конструкторы и деструкторы: потенциальные проблемы

465

Ситуация аналогична передаче параметра по значению, но это енде не конец. Когда программа достигает закрывающей фигурной скобки, объект v должен ис­ чезнуть согласно правилам области действия. Перед этим вызывается деструктор и пытается освободить уже освобожденную динамически распределяемую область памяти. Программа некорректна. Она вышла из-под контроля.

Конструктор копирования, определяемый программистом

Конструктор копирования должен распределять динамическую память для це­ левого объекта в соответствии с операцией конкатенации, о которой рассказыва­ лось в предыдущем разделе. Вот его алгоритм:

1.Скопировать длину символьного массива параметра в поле 1еп целевого объекта.

2. Выделить динамически распределяемую память: установить на нее указатель str целевого объекта.

3. Проверить, успешно ли выделена память. Отказать, если в системе нет памяти.

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

Конструктор копирования, определяемый программистом и позволяющий ре­ шить проблему:

String::String(const

String& s)

/ /

определяемый

программистом

 

 

 

/ /

конструктор

копирования

{ 1еп = s.len;

 

/ /

длина исходного текста

str

= new char[len+1];

/ /

запрос отдельной динамической памяти

i f

(str == NULL) exit(1);

/ /

проверка на успех

strcpy(str, s . s t r ) ;

}

/ /

копирование

исходного текста

Обратите внимание, что параметр s передается по ссылке. Это ссылка на фак­ тический аргумент-объект. При передаче параметра не происходит копирования элементов данных аргумента. Динамическая память фактического аргументаобъекта копируется в динамическую память целевого объекта.

Это менее эффективно, чем элементное копирование, показанное в листин­ ге 11.4. Семантика значений работает медленнее, чем семантика ссылок. Здесь вы имеете дело со значениями, а не со ссылками или указателями. Между тем, семантика значений надежна. Вспомните вариант клиента, который привел ко всем этим проблемам.

String t = v;

/ / нет проблем, если используется конструктор копирования

После выполнения данной строки указатели str в объектах v и t ссылаются на разные области динамически распределяемой памяти. Проблема целостности решена.

Внимание Если среди элементов данных класса встречаются указатели и объекты этого класса работают с динамически распределяемой памятью,

то разработчик класса должен решить, что именно нужно в нем использовать — семантику ссылок или семантику значений. Если необходима семантика значений и требуется инициализировать один объект значением другого объекта, убедитесь, что этот класс имеет определяемый программистом конструктор копирования.

466

Часть II « Объвкгио-ортетмрованиое up

но C'^'i-

u = Это тест.

V = Все нормально.

U = Это тест. Все нормально. V = Все нормально.

t

= Давайте

надеяться

t

= Все нормально.

V

= Давайте

надеяться

V

= Давайте

надеяться

Рис. 11.15.

Результат выполнения программы из листпинга 11.5

Листинг 11.5 показывает программу из листинга 11.4, где класс String определяет свой собственный конструктор, под­ держивающий для инициализации объекта семантику значений. Результат программы представлен на рис. 11.15. Как видно, проблема целостности исчезла. Объекты t и v типа String больше не являются синонимами. Когда объект t изменяется, объект V остается тем же. При завершении вложенной области действия и исчезновении объекта t объект v может использо­ ваться в юшенте. Трассировка кода и его результата доказывает сундествование связи между двумя объектами.

Листинг 11.5. Использование конструктора копирования для инициализации одного объекта с помощью данных другого объекта

#inclucle <iostream> using namespace std;

class String {

 

 

// динамически распределяемый символьный массив

 

char *str;

 

 

 

int

len;

 

 

// закрытая функция

 

 

 

 

 

char* allocate(const char* s)

 

 

памяти для объекта

 

{

char *p = new char[len+1];

// выделение динамической

 

if (p==NULL) exitd);

// проверка науспех; выход в случае неудачи

 

strcpy(p,s);

 

// копирование текста вдинамическую

память

 

return p; }

 

// возврат указателя на динамическую

память

public:

(int length=0);

// конструктор

преобразования/по умолчанию

 

String

 

String(const

char*);

// конструктор

преобразования

 

 

String(const

String& s);

// конструктор

копирования

 

 

"String

0 ;

 

 

// освобождение динамической памяти

 

 

void operator += (const String&);

// конкатенация с другим

объектом

 

 

void modify(const

char*);

// изменение содержимого

массива

 

 

const char* showO

const;

// возврат указателя

массива

 

String::String(int length)

 

 

 

 

 

 

{ len = length;

 

 

// копирование пустой строки вдинамическую памят:

str = allocateC'"); }

String::String

(const char* s)

// определение данных исходного текста

 

{

len = strlen (s);

 

 

 

str = allocate(s.); }

// выделение памяти,

копирование входящего текста

String::String(const

String& s)

// конструктор

копирования

 

 

{

len = s. len;

 

 

// определение длины

исходного текста

 

 

str = allocate (str); }

// выделение памяти,

копирование входящего текста

String::~String()

 

// возврат динамической памяти (не указателя)

{ delete str; }

 

 

void String::operator += (const String& s)

// параметр-ссылка

 

 

 

{

len = strlen(str) + strlen(s.str);

// защита от переполнения

 

 

 

 

char* p = new char[len + 1];

// выделение достаточного объема памяти

 

if (p==NULL)

exitd);

// проверка на успех

части

результата

 

 

strcpy(p,str);

 

// копирование первой

 

 

strcat

(p.s.str);

 

// добавление второй части

 

результата

 

 

delete str;

 

 

// важный шаг

 

 

 

 

 

 

str = p; }

 

 

// теперь р может исчезнуть

 

 

 

 

Глава 11 • Конструкторы и деструкторы: потенциальные проблемы

467

const

char*

String::show()

const

 

// защита данных отизменений

{

return str; }

 

 

 

 

 

 

 

 

 

void

String: :moclify(const

char

a[])

// передача по значению

{

strncpy(str,a,len-1);

 

 

 

 

// защита отпереполнения

 

str[len - 1] = 0;

}

 

 

 

 

// правильное завершение строки

int

mainO

endl «

 

endl;

 

 

 

 

 

 

 

{

cout «

 

 

 

 

 

 

 

 

 

String иС'Проверка");

 

 

 

 

 

 

 

 

String v("Ничего плохого неслучится");

// результат ОК

 

 

cout «

''

u =

«

u.showO

«

endl;

 

 

cout

«

''

V -

«

v.showO

«

endl;

// результат ОК

 

 

u += v;

 

 

«

U.showO

«

endl;

// u.operator+=(v);

 

cout

«

''

u =

/ /

результат ОК

 

 

cout

«

''

V =

«

V.showO «

endl;

/ /

ОК - передача по ссылке

 

v.modify("Давайте надеяться налучшее.");

/ /

порча содержимого памяти

 

{

String t = v;

 

 

 

 

 

 

 

 

 

 

t.modify("Ничего плохого не случится")

/ /

меняем только

t

 

 

cout «

" t = " « t.showO

«

endl;

/ /

ОК, корректный

результат

 

 

cout «

" V = " «V . showO

«

endl; }

/ /

V также изменился

 

cout «

" V = " «

V.showO «

endl;

/ /

t больше нет, v теряет память

 

return 0;

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

ВЛистинге 11.5 класс String имеет три конструктора. Они выделяют память

вдинамически распределяемой области и инициализируют ее содержимое. В пер­ вом конструкторе преобразования инициализирующие данные — это пустая стро­ ка, завершаемая нулем. Во втором конструкторе преобразования это символьный массив, подставляемый клиентом в фактическом аргументе. В конструкторе копи­ рования инициализирующими данными является символьный массив внутри объ­ екта, поставляемого клиентом. Поскольку этот массив находится в динамической памяти, он не имеет имени, а ссылка на него осуществляется через указатель str. Так как параметр-объект s принадлежит к тому же классу String, что и целевой инициализируемый объект, конструктор копирования имеет право доступа к этому закрытому указателю str с помощью уточненного имени s. str.

Вполне естественно, что разные конструкторы используют аналогичные алго­ ритмы, так как вид полученного в результате объекта не должен зависеть от вызванного при его создании конструктора. Если класс содержит один или два конструктора, повторите код. Если алгоритм используется очень часто, то про­ граммисты обычно инкапсулируют его в частную функцию и вызывают ее из разных компонентных функций. Данная функция должна быть закрытой, так как клиент не заинтересован в прямой работе с памятью объекта. Это детали нижнего уровня, которые не должны запутывать алгоритм клиента и занимающегося им программиста. Подобная закрытая функция представлена в листинге 11.5. Когда она копирует свой параметр в выделенную динамическую память, то использует имя указателя р.

char*

allocate(const char* s)

// закрытая функция

{ char

*p = new char[len+1];

// выделение памяти для объекта

if (p==NULL) exitd);

// проверка на успех; выход, если неповезло

strcpy(p,s);

// копирование текста в динамическую память

return p; }

// возврат указателя надинамическую память

468

Часть II о Объвкто-ортешировамиое

програтмтрошттв на С4-4-

 

Листинг 11.5 показывает, что первый конструктор преобразования передает

 

функции allocateO пустую строку, второй отправляет свой собственный пара­

 

метр— символьный массив, а конструктор копирования передает

allocateO

 

символьный массив своего параметра, т. е. s. str.

 

 

 

Когда один объект инициализирует другой объект, вызывается конструктор

 

копирования. Это неизбежно. Вопрос в том, какой именно конструктор вызывает­

 

ся. Если класс не предусматривает свой собственный конструктор копирования,

 

то компилятор генерирует вызов системного конструктора, который копирует

 

элементы данных объекта. Если для объектов этого класса не выделяется динами­

 

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

 

индивидуальные сегменты динамически распределяемой памяти (семантику зна­

 

чений), то применение предусмотренного системой конструктора копирования

 

подрывает целостность приложения. Чтобы сохранить целостность программы,

 

в классе следует использовать собственный конструктор копирования, который

 

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

 

В предыдущем предложении "следует предусмотреть" подчеркивает взаимо­

 

связь "клиент-сервер" между разными сегментами программы C + + и ме>кду раз­

 

ными уровнями понимания. С помощью клиентской программы обрабатываются

 

объекты. Сервер поддерживает клиента, реализуя вызываемые клиентом функции-

 

члены. Конструкторы вызываются неявно, но при этом не изменяется соотноше­

 

ние клиент-сервер.

 

 

 

 

 

Если в приложении необходима семантика копирования, в классах с динамиче­

 

ским управлением памятью используются конструкторы копирования для других

 

контекстов, когда один объект инициализирует другой. Одним из таких контекстов

 

является передача параметров-объектов по значению. При наличии соответствую­

 

щего конструктора копирования будет замечательно работать первая версия пере­

 

груженной операции конкатенации из листинга 11.3.

 

 

 

void

String::operator += (const String s)

/ /

параметр-объект

 

{ len

= strlen(str) + s t r l e n ( s . s t r ) ;

 

/ /

общая длина

 

 

char *p = new char[len + 1 ] ;

/ /

распределение динамической памяти

 

i f

(p==NULL) exit(1);

/ /

проверка на успех

 

 

strcpy(p,str);

/ /

копирование первой части

результата

 

strcat (p . s . str);

/ /

добавление

второй части результата

 

delete str;

/ /

важный шаг

 

 

 

str

= р; }

/ /

str указывает на новую память

При вызове данной функции и создании фактического аргумента вызывается определенный программистом конструктор копирования. Он выделяет динамиче­ скую память для формального параметра s. Когда эта функция завершает работу и для формального параметра вызывается деструктор, освобождается его собст­ венная динамическая память, а не динамическая память, принадлежащая факти­ ческому аргументу. Проблема, связанная с целостностью, исчезает. Однако сохраняется проблема производительности. Когда параметр передается по значе­ нию, вызов операции конкатенации предусматривает создание объекта, вызов конструкторов копирования, выделение динамически распределяемой памяти, копирование символов из одного объекта в другой, вызов деструктора и осво­ бождение динамической памяти. Для вызова по ссылке не требуется проведение подобных операций. Семантика ссылок решает проблему производительности, поскольку устраняется излишнее копирование.

О с т о р о ж н о ! Не передавайте объекты функциям по значению. Если объекты имеют внутренние указатели и работают с динамически распределяемой памятью, не передавайте эти объекты по значению. Если же необходимо отправить такие объекты по значению, определите конструктор копирования.

Глава 11 • Конструкторы и деструкторы: потенциальные проблемы

469

Возврат по значению

При возврате объекта из функции по значению применяйте семантику значе­ ний. В главе 10 уже обсуждались классы, не работающие с динамически распреде­ ляемой памятью. Поскольку ситуация с возвратом объекта из функции в точности такая же, как и при инициализации одного объекта другим, рассмотрим ее.

Исходный

 

 

Листинг 11.6 представляет еще одну версию

 

 

класса String. В каждый конструктор включены

Исходный:

 

отладочные операторы и добавлена перегруженная

Исходный;

 

операция сравнения, реализованная как функция-

Исходный;

 

Создан:

Атланта'

 

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

Создан:

Бостон'

 

enterDataO

и обновлена функция main(). Програм­

Создан: 'Чикаго'

 

Создан:

Денвер'

 

ма просит

пользователя ввести название города

Введите

название города для поиска: Бостон

и ищет это имя в базе данных. Для простоты база

Создан:

'Бостон'

 

данных определена в функции main() как массив

Город

Бостон найден

 

 

 

 

 

символов, и используется простой последователь­

Рис.

11,16. Результат

выполнения

ный поиск. Результаты выполнения показаны на

\\ \а

 

 

 

программы

из листпинга 11.6

Р^^- 11-1Ь.

 

Листинг 11.6. Использование конструктора копирования для возврата объекта из функции

#include <iostream> using namespace std;

class

String {

 

 

 

// динамически

распределяемый символьный массив

 

char

*str;

 

 

 

 

int

len;

 

 

 

// закрытая функция

 

 

 

char*

allocate(const char* s)

 

 

 

 

{

char

*p = new char[len+1];

 

// выделение динамической памяти для объекта

 

i f

(p==NULL) e x i t d ) ;

 

 

// проверка науспех; выход в случае неудачи

 

strcpy(p,s);

 

 

// копирование текста вдинамическую

память

 

 

return p;

}

 

 

// возврат указателя надинамическую

память

public-

 

 

 

 

 

// конструктор преобразования/по умолчанию

 

String

(int

length=0);

 

 

 

String(const

char*);

 

 

// конструктор

преобразования

 

 

String(const

String& s);

 

 

// конструктор

копирования

 

 

"String

0 ;

 

 

 

// освобождение динамической памяти

 

 

void operator += (const String&);

// конкатенация с другим объектом

 

 

void modify(const char*);

 

 

// изменение содержимого массива

 

 

bool operator == (const String&) const;

// сравнение содержимого

 

 

const char* showO const;

 

 

// возврат указателя

массива

 

} ;

 

 

 

 

 

 

 

 

 

 

String::String(int length)

 

 

 

 

 

 

{

len = length;

 

 

// копирование пустой строки вдинамическую память

 

str = allocateC'");

str «

 

 

cout «

" Исходный: '" «

'"\n"; }

 

 

 

String::String(const char* s)

 

// определение длины

исходного текста

 

{

len = strlen(s);

 

 

 

 

str = allocate(s);

str

 

// выделение памяти, копирование текста

 

cout «

" Созданный: '" «

« '\п"; }

 

 

 

String::String(const String& s)

 

// конструктор

копирования

 

{

len = s.len;

 

 

 

// определение длины

исходного текста

 

 

str = allocate(s.str);

«

 

// выделен1де памяти,

копирование текста

 

cout «

" Скопированный:

str « "'\п"; }

 

 

 

Соседние файлы в предмете Программирование на C++