Читаем Программирование полностью

• В конце существования объекта деструктор освобождает все ресурсы, которыми владел объект.

Типичным примером является пара “конструктор–деструктор” в классе vector, которая управляет свободной памятью. Мы еще вернемся к этой теме в разделе 19.5. А пока рассмотрим важное сочетание механизма управления свободной памятью и иерархии классов.

Shape* fct()

{

  Text tt(Point(200,200),"Annemarie");

  // ...

  Shape* p = new Text(Point(100,100),"Nicholas");

  return p;

}

void f()

{

  Shape* q = fct();

  // ...

  delete q;

}

Этот код выглядит логичным — и он действительно логичен. Все работает, но посмотрите, как именно работает, ведь этот код является примером элегантного, важного и простого метода. При выходе из функции fct() объект tt класса Text (см. раздел 3.11), существующий в ней, уничтожается вполне корректно. Класс Text имеет член типа string, у которого обязательно нужно вызвать деструктор, — класс string занимает и освобождает память примерно так же, как и класс vector. Для объекта tt это просто; компилятор вызывает сгенерированный деструктор класса Text, как описано в разделе 17.5.1. А что можно сказать об объекте класса Text возвращаемом функцией fct()? Вызывающая функция f() понятия не имеет о том, что указатель q ссылается на объект класса Text; ей известно лишь, что он ссылается на объект класса Shape. Как же инструкция delete q сможет вызвать деструктор класса Text?

В разделе 14.2.1 мы вскользь упомянули о том, что класс Shape имеет деструктор. Фактически в классе Shape есть виртуальный деструктор. В этом все дело. Когда мы выполняем инструкцию delete q, оператор delete анализирует тип указателя q, чтобы увидеть, нужно ли вызывать деструктор, и при необходимости он его вызывает. Итак, инструкция delete q вызывает деструктор ~Shape() класса Shape. Однако деструктор ~Shape() является виртуальным, поэтому с помощью механизма вызова виртуальной функции (см. раздел 17.3.1) он вызывает деструктор класса, производного от класса Shape, в данном случае деструктор ~Text(). Если бы деструктор Shape::~Shape() не был виртуальным, то деструктор Text::~Text() не был бы вызван и член класса Text, имеющий тип string, не был бы правильно уничтожен.

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

1. Если класс имеет виртуальную функцию, то, скорее всего, он будет использован в качестве базового.

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

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

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

ПОПРОБУЙТЕ

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

<p id="AutBody_Root309"><strong> 17.6. Доступ к элементам</strong></p>

Для того чтобы нам было удобно работать с классом vector, нужно читать и записывать элементы. Для начала рассмотрим простые функции-члены get() и set().

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

class vector {

  int sz;        // размер

  double* elem;  // указатель на элементы

public:

  vector(int s):

  sz(s), elem(new double[s]) { /* */} // конструктор

  ~vector() { delete[] elem; }        // деструктор

  int size() const { return sz; }     // текущий

                                      // размер

  double get(int n) const { return elem[n]; } // доступ: чтение

  void set(int n, double v) { elem[n]=v; }    // доступ: запись

};

Функции get() и set() обеспечивают доступ к элементам, применяя оператор [] к указателю elem.

Теперь мы можем создать вектор, состоящий из чисел типа double, и использовать его.

vector v(5);

for (int i=0; i

  v.set(i,1.1*i);

  cout << "v[" << i << "]==" << v.get(i) << '\n';

}

Результаты выглядят так:

v[0]==0

v[1]==1.1

v[2]==2.2

v[3]==3.3

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже