Бьерн Страуструп - Абстракция данных в языке С++ - Скрытое управление памятью

ОГЛАВЛЕНИЕ

 

        Скрытое управление памятью

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