Страница 12 из 25 Скрытое управление памятью Конструкторы и деструкторы не могут полностью скрыть детали управления памятью от пользователя класса. Если объект копируется, либо посредством явного присваиваниям, либо при передаче функции в качестве аргумента, указатели на вторичную структуру данных также копируются. Это иногда нежелательно. Рассмотрим проблему семантики передачи значений для простого типа данных s t r i n g . Пользователь видит s t r i n g как один объект, однако его реализация состоит из двух частей, как это приведено выше. После выполнения присваивания s 1 = s 2 оба объекта s t r i n g ссылаются на то же самое представление, в то время как ссылка на память, использованная для старого представления s 1 , теряется. Во избежании этого оператор присваивания может быть перегружен. class string { char *rep; void operator = (string); . . . }; void string.operator = (string source) { if (rep !=source.rep) { delete rep; rep = new char[strlen(source.rep) + 1]; strcpy (rep, source.rep); } } Так как функции нужно модифицировать, объект-приемник (типа s t r i n g ) лучше всего написать составляющую функцию, принимающую исходный объект s t r i n g в качестве аргумента. Тогда присваивание s 1 = s2 будет интерпретироваться как s 1. o p e r a t o r = ( s 2 ). Это оставляет в стороне проблему, что делать с инициализацией и аргументами функции. Рассмотрим: string s1 = "asdf"; string s2 = s1; do_something(s2); В данном случае объекты типа s t r i n g s 1 , s 2 и аргумент функции d o _ s o m e t h i n g остаются с одними тем же представлением r e p . Стандартное побитовое копирование совершенно не сохраняет желаемую семантику значений для типа s t r i n g . Семантика передачи аргументов инициализации идентична : обе предполагают копирование объекта в неинициализированную переменную. Она отличается от семантики присваивания только в том, что объект, которому присваивается значение, предположительно содержит значение, а объект инициализации - нет. В частности, конструкторы используются для передачи аргументов точно также, кака и при инициализации. Следовательно, нежелательное побитовое копирование может быть обойдено, если мы определим конструктор для выполнения подходящей операции копирования. К сожалению, использование очевидного конструктора class string { ... string (string); }; ведет к бесконечной рекурсии, и поэтому незаконно. Для решения этой проблемы вводится новый тип "ссылка" (*1). Синтаксически он определяется знаком &, который используется тем же образом, как и знак указателя *. Если переменная объявлена как имеющая тип &Т то есть как ссылка на тип Т, она может быть инициализирована как указателем на тип Т, так и объектом типа Т. В последнем случае неявно применяется операция взятия адреса &. Например: int x; int &r1 = &x; int &r2 = x; Здесь адрес x присваивается как r1, так и r2. При использовании ссылка неявно преобразуется, так, например: r1 = r2 означает копирование объекта, указываемого r2 в объект, указываемый r1. Заметим, что инициализация ссылок совершенно отлична от присваивания им. _______________________ (*1) - В оригинале - "reference", не путать с "pointer" - указателем. прим. переводчика. Используя ссылки, класс s t r i n g может быть объявлен, например, так: clfss string { char *rep; string(char *); string(string &); ~string(); void operator=(string &); . . . }; string(string &source) { rep = new char[strlen(source.rep) + 1]; strcpy(rep, source.rep); } Инициализация одного объекта s t r i n g другим и передача s t r i n g в качестве аргумента будет теперь вызывать обращение к конструктору s t r i n g ( s t r i n g & ) , который корректно дублирует представление типа. Операция присваивания для типа s t r i n g может быть переопределена, используя преимущества ссылок. Например: void string.operator = (string &source) { if (this != &source) { delete rep; rep = new char[strlen(source.rep) + 1]; strcpy(rep, source.rep); } } Данный тип s t r i n g во многих приложениях не будет достаточно эффективен. Нетрудно, однако, модифицировать его так, что представление типа будет копироваться только в случае необходимости, а в остальных случаях - разделяться. |