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

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

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

470

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

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; }

bool String::operator==(const String& s) const { return strcmp(str,s.str)==0: }

const char* String::show() const { return str; }

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

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

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

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

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

//копирование первой части результата

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

//важный шаг

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

// сравнение содержимого //при совпадении strcmp возвращает О

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

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

 

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

{

strcpy(str,a, len-1);

 

 

 

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

 

str[len-1] =0; }

 

 

 

 

 

 

 

String

enterDataO

 

 

 

 

 

 

// запрос пользователю

{

cout «

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

 

char data[200];

 

 

 

 

// грубое решение

 

cin »

data;

}

 

 

'

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

 

return String(data);

 

 

//вызов конструктора

int mainO

 

 

 

 

 

 

 

 

{

enum {MAX = 4;

 

 

 

 

 

 

 

 

String data[4];

 

 

 

 

/ /

база данных объекта

 

char *c[4] = { "Атланта", "Бостон"

'Чикаго",

"Денвер"

};

 

for (int j=0; j<MAX;

j++)

 

 

 

 

 

 

 

{ data[j] +=c[j]; }

 

 

 

 

/ /

data[j].operator+=(c[j]);

 

String u = enterDataO;

 

 

 

/ /

аварийно завершается без конструктора

 

 

 

 

 

 

 

 

 

/ /

копирования

 

i nt

i ;

 

 

 

 

 

 

 

 

 

 

for

(i=0; i < MAX; i++)

 

 

 

/ /

i определено вне цикла

 

{

i f

(data[i] == u) break;

}

 

 

/ /

выход,

если строка найдена

 

i f ( i ==

MAX)

 

 

 

не найден\п";

 

 

cout

«

"Город « u.showO

«

"

 

 

else

 

 

 

 

 

' найден\п";

 

 

 

cout

«

" Город «

U.showO

«

 

 

return 0;

}

Когда в функции main() создается массив объектов, для каждого из них вызы­ вается заданный по умолчанию конструктор String (например, первый конструк­ тор преобразования со значением по умолчанию). Конструктор выделяет память для пустой строки нулевой дайны и выводит сообщение "Исходный". Когда вызы­ вается функция operator+=() и к содержимому каждого объекта добавляются на­ звания городов, массив символов передается операции сравнения как параметр. Перегруженная операторная функция ожидает получения параметра String. Сле­ довательно, вызывается второй конструктор преобразования, который для каждо­ го элемента массива выводит сообидение "Создан".

 

 

 

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

471

 

 

 

 

Вызывается функция enterData(). Она запрашивает у пользователя название

 

 

 

города и передает его как аргумент конструктору преобразования String. На

 

 

 

экране появляется выводимое конструктором сообщение "Создан". Поскольку

 

 

 

объект U в функции main() создается только при вызове enterData(), вызываемый

 

 

 

в enterData() конструктор используется как конструктор для объекта и в функции

 

 

 

main(). Конструктор копирования не вызывается. Объекты String работают с ди­

 

 

 

намической памятью, однако целостность программы сохраняется. Конструктор

 

 

 

преобразования выделяет для объекта и в функции main() индивидуальную память

 

 

 

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

 

 

 

 

 

 

 

 

 

Измените функцию enterData(). Добавьте еще один локальный объект для

 

 

 

хранения данных пользователей.

 

 

 

 

 

 

 

 

 

String

enterDataO

 

 

 

 

 

 

 

 

 

 

{ cout

« "Введите название города для поиска: ";

 

/ /

запрос пользователю

 

 

 

char data[200];

 

/ /

грубое решение

 

 

 

 

cin »

data;

 

/ /

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

 

 

 

String X = data;

 

/ /

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

 

 

 

return х; }

 

/ /

конструктор копирования

 

 

 

Изменения незначительны. Если бы х была переменной встроенного типа, то

 

 

 

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

распределяемой

 

 

 

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

 

 

 

вания. Между тем, когда функция завершает работу, объект и в функции main()

 

 

 

инициализируется с помощью конструктора копирования. Если определяемый

 

 

 

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

 

 

 

 

 

 

темный конструктор копирования. Он копирует

Исходный: "

 

 

 

 

элементы данных объекта х

в элементы

данных

 

 

 

 

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

Исходный: ''

 

 

 

 

Исходный: ''

 

 

 

 

Указатели str объектов и и х ссылаются на одну

Исходный: ''

 

 

 

 

область динамически

распределяемой

памяти.

Создан:

'Атланта'

 

 

 

Когда функция enterDataO

завершает

работу

Создан:

'Бостон'

 

 

 

Создан:

'Чикаго'

 

 

 

и объект X исчезает, вызывается деструктор String.

Создан:

'Денвер'

 

 

 

Он

освобождает динамическую

область

памяти,

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

на

которую ссылается указатель

str в объекте х.

Создан:

'Москва'

 

 

 

Скопирован:

'Москва'

 

 

Динамическая память

объекта и освобождается

Город Москва

найден

 

 

при создании объекта.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 11.17.

Результат

выполнения

 

В результате такого подхода программа аварий­

но завершает работу. Однако она уже некорректна.

 

 

программы

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

Нужен определяемый программистом конструктор

 

 

с

модифицированной

копирования.

 

 

 

 

 

 

 

функцией

enterDataO

 

 

 

 

 

 

 

и

конструктором

 

Все работает нормально, когда подставляется

 

 

копирования

 

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

 

 

 

 

 

 

вания. Пример результата выполнения программы

 

 

 

 

 

 

представлен на рис. 11.17.

 

 

 

 

 

 

Вывод показывает, что после ввода строки пользователем для локального

 

 

 

объектах в функции main() вызывается конструктор преобразования, затем

 

 

 

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

и в функции main(). Данная

 

 

 

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

 

 

 

не так, как предыдущая. И еще важнее, что, если бы переменные х и и были

 

 

 

встроенного типа, то такие изменения не повлияли бы на поведение программы.

 

 

 

Подобные эксперименты со встроенными типами помогают программисту найти

 

 

 

свое решение. Несмотря на все усилия, встроенные и определяемые программи­

 

 

 

стом типы интерпретируются

в C + + по-разному. Работа с объектами требует

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

I 472 I

Часть II « Объектно-ориентированное програттшроваитв на С^-^

шшяшя^шшшшшшшшшшшшшшшшшшш/шшшшшшшшшшшшшшшш^шшшшшшшшшшшшшшш^шшшшшшшшшш^^

Ограничения для эффективности конструктора копирования

Хотелось бы внести в программу еще одно небольшое изменение, на этот раз в клиенте. Вместо определения объекта и в функции main() и немедленной его инициализации после определения этого объекта (с помощью конструктора по умолчанию) присвоим ему то, что вводит пользователь при вызове функции enterDataO.

i nt

mainO

 

 

 

 

{ enum { MAX = 4) ;

 

 

 

/ /

создание базы данных с названиями

городов

 

/ /

String

U = enterDataO;

/ /

аварийно завершается без

 

 

 

/ /

конструктора

копирования

String и;

 

/ /

конструктор

по умолчанию

U = enterDataO;

/ /

аварийно завершается: конструктор

 

 

 

/ /

копирования не поможет

/ /

поиск

города, вывод результатов

 

 

 

return 0;

 

 

 

 

}

 

 

 

 

 

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

Перегрузка операции присваивания

в некоторых случаях инициализация и присваивание объекта в C++ различа­ ются. Если приходится иметь дело со встроенными типами данных, такое различие часто бывает академическим. Рассмотрим пример:

int V = 5; int u = v; / / переменная инициализируется

Сравните его с таким примером:

int V = 5; int u; u = v; / / присваивание переменной u

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

String

V =

"Hello";

String

u = v;

/ /

объект

u

инициализируется

String

V =

"Hello";

String

u; u = v;

/ /

объект

u

присваивается

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

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

473

Проблемы системной операции присваивания

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

Подобно подставляемому системой конструктору копирования, эта предусмот­ ренная системой операция присваивания всегда доступна. Для классов без дина­ мического управления памятью (например, Complex, Rational, Rectangle) такая "системная" операция присваивания вполне подходит. Для классов, динамически управляющих своей памятью, данная подставляемая системой операция присваи­ вания вызывает проблемы.

Когда присваивание выполняется для объектов String, копируются отдельные элементы данных объектов. Указатель str объекта в левой части присваивания ссылается на то же место в динамически распределяемой памяти, что и указа­ тель str объекта в правой части операции присваивания. Объекты становятся си­ нонимами. Если изменить один объект, например и, то меняется и другой объект, в данном случае v.

Когда объект, например и, уничтожается операцией delete, для него вызыва­ ется деструктор. Освобождается память, на которую ссылается указатель str. В результате другой объект (в данном случае v) теряет свою динамическую память, хотя в программе он еще существует. Любое использование такого объекта будет некорректным. Когда этот объект уничтожается, для него вызывается деструктор. Он пытается освободить память, на которую ссылается указатель st г. Однако эта память уже освобождена. Как пояснялось ранее, попытка повторного освобожде­ ния динамически распределяемой памяти приводит к непредсказуемому поведению программы. Такие действия семантически некорректны, хотя синтаксически пра­ вильны.

Поиск причины проблемы будет затруднительным, так как трудности не связа­ ны с результатами выполнения программы. Конструктор копирования не позволя­ ет от нее избавиться, поскольку во время операции присваивания он не вызывается. В C + + присваивание и инициализация — разные понятия.

Перегруженная операция присваивания: первая версия (утечка памяти)

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

Встроенная операция присваивания в С+Н двухместная операция (с двумя операндами). Она содержит операнды в левой и в правой частях. Это же относится к перегруженной операции присваивания, определяемой программистом. Следо­ вательно, интерфейс такой операции аналогичен интерфейсу конструктора копи­ рования: объект в левой части операции является получателем сообщения, а объект в правой части — параметром.

U = v; / / u.operator=(v);

Это означает, что перегруженная операция присваивания для класса String должна иметь следуюндий интерфейс:

void String::operator - (const String& s);

/ / операция присваивания

474

Часть II * Объектно-:-',

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

1.Копирование длины символьного массива (параметра) в элемент 1еп целевого объекта.

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

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

,4 . Копирование символов из объекта-параметра

ввыделенную область памяти.

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

Ниже приведена версия операции присваивания.'Хотя она работает медленнее, чем операция присваивания, предусмотренная системой, но сохраняет семантику значений.

void

String::operator = (const

String& s)

{ len = s.len;

 

/ /

копирование данных, отличных от указателя

str

= new char [len

+ 1];

/ /

выделение собственной области

 

 

 

/ /

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

i f

(str == NULL) exit(1);

/ /

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

strcpy(str,S . str);

}

/ /

копирование данных в динамически

 

 

 

/ /

распределяемую область

Такая операция присваивания интерпретирует целевой объект точно так же, как конструктор копирования, т. е. как будто это новый объект, не имеющий предыстории. В случае конструктора копирования так оно и есть, однако в случае операции присваивания это не так. Целевой объект и уже был создан ранее, т. е. конструктор вызывался при создании объекта и указатель st г был установлен на область в динамически распределяемой памяти. Операция присваивания игнори­ рует эту динамически распределяемую память. Она устанавливает указатель str на другую область в динамической памяти, а память, выделенная ранее для объек­ та, теряется. Следовательно, данная операция присваивания приводит к утечке памяти. Это вторая опасность в программе C+ + . Первая неприятность связана с повторным освобождением памяти.

Каков же выход? В отличие от конструктора копирования, операция присваи­ вания должна предварительно освобождать ресурсы (память), используемые целе­ вым объектом. Ниже — улучшенная версия перегруженной операции присваивания:

void

String::operator - (const

String& s)

{ delete str;

 

/ /

это нельзя сделать в конструкторе копирования

len = s.len;

 

/ /

копирование данных, отличных от указателя

str

= new char[len

+ 1 ] ;

/ /

выделение собственной области

 

 

 

/ /

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

i f

(str == NULL) exit(1);

/ /

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

strcpy(str,S . str);

}

/ /

копирование данных в динамически

 

 

 

/ /

распределенную область

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

475

Перегруженная операция присваивания: следующая версия (самоприсваивание)

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

U = и;

/ / U.operator = (и); такое приходится делать нечасто, не так ли?

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

Как ни странно, подобное "самоприсваивание" — не такая уж редкость. Оно часто встречается в алгоритмах сортировки и в операциях с указателями. Для предотвращения возврата динамически распределяемой памяти объекта операция проверяет, ссылается параметр на тот адрес, где находится целевой объект, или нет. Для этого используется указатель this.

void

String::operator = (const

String& s)

{ i f

(&s == this)

return;

/ /

предотвращает потерю памяти

 

 

 

 

/ /

при самоприсваивании

delete str;

 

 

/ /

это нельзя сделать в конструкторе копирования

1еп = s.len;

 

 

//,копирование данных, отличных от указателя

str

= new char[len

+ 1 ] ;

/ /

выделение собственной области

 

 

 

 

/ /

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

i f

(str == NULL)

exit(1);

/ /

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

s t r c p y ( s t r , s . s t r ) ;

}

/ /

копирование данных в динамически

 

 

 

 

/ /

распределяемую область

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

Еще одно решение состоит в проверке установки указателей str в целевом объекте и в объекте-параметре на одну и ту же область в динамически распреде­ ляемой памяти. В операторной функции данная проверка может выглядеть так:

i f (str == s . str) return; / / одна и та же динамически распределяемая память?

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

Перегруженная операция присваивания: еще одна версия (цепочка выражений)

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

t = U = v;

/ / возвращает тип void - это не поддерживается

return *this; }
i f (str == NULL) e x i t d ) ; strcpy(str,s . str);
delete str; len = s.len;
str = new char[len + 1];
String String::operator = (const String& s) / / возвращает объект { i f (&s == this) return *this; / / предотвращает потерю памяти
/ / при самоприсваивании / / это нельзя сделать в конструкторе копирования
/ / копирование данных, отличных от указателя / / выделение собственной области / / в динамической памяти / / проверка на успех
/ / копирование данных в динамически / / распределяемой области
Это означает, что операция присваивания должна возвраи;ать значение, подходя­ щее для использования фактического аргумента в другой операции присваивания, т. е. она должна возвращать значение того типа, которому принадлежит операция присваивания.

476

Часть li * Объектно-ориентированное програ1М1^ироеание но С'^^

В клиенте всегда можно указать последовательность двухместных операций:

v;

//двухместная

операция:

и. operator=(v);

и;

//двухместная

операция:

t. operator=(u);

Между тем снова встает вопрос об одинаковой интерпретации встроенных типов и типов, определяемых программистом. Для переменных встроенных типов такая цепочка в программе C++ вполне законна. Следовательно, она должна допускаться для всех переменных определяемых программистом типов.

Операция присваивания ассоциативна справа налево, поэтому смысл цепочки следуюидий:

t = (и = v);

/ / возвращает тип void - это не поддерживается

 

 

Листинг 11.7 представляет модифицированную версию программы из листин­

 

 

га 11.6. Здесь добавлена перегруженная операция присваивания. Для запроса

 

 

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

 

 

ется закрытая функция allocate(). Чтобы сократить объем отладочного вывода,

 

 

здесь удалены операторы вывода сообщений "Исходный" из используемого по

 

 

умолчанию конструктора. Кроме того, исключен вызов операции конкатенации

 

 

 

 

operator+=() в цикле клиента, загружающий на­

Создан:

'Атланта'

 

звания городов в базу данных. Она заменена на

Присвоен: 'Атланта'

 

операцию присваивания. Результат программы по­

Скопирован:

'Атланта'

 

казан на рис. 11.18.

 

Создан:

'Бостон'

 

Как можно видеть, проблема целостности ис­

Присвоен: 'Бостон'

 

Скопирован:

'Бостон'

 

чезла. С объектами типа String можно работать

Создан:

'Чикаго'

 

точно так же, как с объектами встроенных число­

Присвоен: 'Чикаго'

 

Скопирован:

'Чикаго'

 

вых типов. Допускается их создание без инициали­

Создан:

'Денвер'

 

зации, инициализация из символьного массива или

Присвоен: 'Денвер'

 

Скопирован:

'Денвер'

поиска: Денвер

из другого, ранее созданного объекта String. Обра­

Введите

название города для

тите внимание, что C++ не позволяет делать это

Создан:

'Денвер'

 

с массивами: массивы C + +

реализуют семантику

Присвоен: 'Денвер'

 

Скопирован:

'Денвер'

 

ссылок, а не семантику значений.

Город Денвер

найден

 

Можно добавить в класс столько арифметиче­

Рис. 11-18-

Результат

выполнения

ских операций, сколько требуется (например, для

сложения

объектов String,

их вычитания, умно­

 

 

программы

из лист>ипга 11.7

жения и

т.д.). Между тем

не нужно забывать

 

 

 

 

о приложении программиста.

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

477

Л и с т и нг 11.7. Класс String с перегруженной операцией присваивания

#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-

String (int length=0);

String(const char*); String(const String& s); ""String 0 ;

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

bool operator == (const String&) const; const char* showO const;

} ;

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

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

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

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

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

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

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

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

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

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

//операция присваивания

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

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

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

String::String(int length)

 

 

{

len = length;

 

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

 

str = allocateC'"); }

 

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);

 

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

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

 

cout «

" Скопированный: '" « 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; }

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

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

 

{

if (s ==this) return *this;

// проверка на самоприсваивание

 

delete str;

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

 

len = s.len;

// копирование данных, отличных от указателя

 

str = allocate(s.str);

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

 

cout «

" Присвоенный: '" « str « '"\n";

// только для отладки

 

return

*this; }

// возврат результата клиенту

478

 

Часть II • Объектно-ориентированное программирование на C-t-i-

bool String::operator==(const String& s) const

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

{

return strcmp(str,s.str)==0: }

 

// при совпадении strcmp возвращает О

const char* String::show() const

 

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

{

return str; }

 

 

 

 

 

 

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

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

{

strcpy(str,a,len-1);

 

 

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

 

str[len-1] = 0; }

 

 

 

 

String enterDataO

 

 

 

// запрос пользователю

{

cout «

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

 

char clata[200];

 

 

 

 

// грубое решение

 

cin » data;

 

 

 

 

 

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

 

return String(data); }

 

 

// вызов конструктора

int mainO

 

 

endl;

 

 

 

{

cout «endl «

 

 

 

 

enum {MAX = 4 }

;

 

 

 

// база данных объекта

 

String data[4];

 

 

 

 

 

char

*c[4] = {

"Атланта", 'Бостон", "Чикаго"

"Денвер" };

 

for

(int

j=0;

j<MAX;

j++)

 

 

// присваивание:

 

{ data[j]

= c [ j ] ; }

 

 

 

data[j].operator+=(c[j]);

 

 

 

 

String u;

int

i ;

 

 

 

 

// нужно присваивание, нет

 

u = enterDataO;

 

 

 

 

for (i=0; i < MAX; i++)

 

 

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

 

 

 

// i определено вне цикла

 

{ if (data[i] == u) break; }

 

 

// выход, если строка найдена

(data[i].operator==(u))

 

 

 

 

if (i == MAX)

 

 

u.showO

«

' не найден\п"

 

 

cout «

"Город «

 

 

else

 

" Город «

U.showO

«

' найден\п";

 

 

cout «

 

 

return 0;

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

Вопросы производительности

Если нужно инициализировать один объект с помоидью другого объекта (при определении, передаче, параметров по значению или возврате значения из функции), то следует предусмотреть конструктор копирования. Если требуется присвоить один объект другому, используйте перегруженную операцию присваивания.

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

Предотвратить появление проблем можно следуюш^1м образом. Разработчики должны изучить требования клиентской программы и понять последствия различ­ ных решений.

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

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

479

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

1. Вызов конструктора преобразования для параметра функции operator=()

2. Вызов самой функции operator=()

3. Вызов конструктора копирования для возврата по значению из операции присваивания

Несмотря на все усилия, сохраняется большая разница между объектами клас­ са и встроенными значениям. В данном цикле, если массивы clata[] и с[] имеют компоненты встроенных типов, оператор будет только один. Для класса String все иначе: тело цикла представляет три вызова функции:

for (int j=0; j<MAX;

j++)

{ clata[j]=c[j]; }

//присваивание: data[j] . operator=(String(c[[j]));

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

Первое решение:

больше перегруженных операций

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

String String::operator=(const char s [ ] )

/ / массив как параметр

{ delete

str;

/ / этого не следует делать в конструкторе копирования

1еп = strlen(s);

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

str = allocate(s);

cout «

"Присвоен: '" «

str << " '\п";

// для отладки

return *this; }

 

 

 

 

Если нужно поддерживать операцию присваи­

 

 

вания для символьных массивов и для объектов

 

 

St riag, следует дважды перегрузить операцию при­

 

 

сваивания: для объекта String и для символьного

 

 

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

 

 

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

 

 

операцией присваивания

показан на рис. 11.19.

 

 

В отладочном сообщении второй операции присва­

 

 

ивания добавлено несколько пробелов. Вы можете

 

 

различать сообщения, выведенные первой опера­

 

 

цией присваивания (с параметром типа String),

 

 

и второй операцией присваивания (с параметром

 

 

типа символьного массива).

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