Бьерн Страуструп - Абстракция данных в языке С++ - Скрытое управление памятью
ОГЛАВЛЕНИЕ
Скрытое управление памятью
Конструкторы и деструкторы не могут полностью скрыть детали
управления памятью от пользователя класса. Если объект копируется,
либо посредством явного присваиваниям, либо при передаче функции
в качестве аргумента, указатели на вторичную структуру данных
также копируются. Это иногда нежелательно. Рассмотрим проблему
семантики передачи значений для простого типа данных 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 во многих приложениях не будет
достаточно эффективен. Нетрудно, однако, модифицировать его так,
что представление типа будет копироваться только в случае
необходимости, а в остальных случаях - разделяться.