Вход/Регистрация
Программирование. Принципы и практика использования C++ Исправленное издание
вернуться

Страуструп Бьерн

Шрифт:

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
. Они никогда не вызываются непосредственно. Это позволяет избежать довольно трудоемкой работы.

ПОПРОБУЙТЕ

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

17.6. Доступ к элементам

Для того чтобы нам было удобно работать с классом

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.size; ++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

v[4]==4.4

Данный вариант класса

vector
чрезмерно прост, а код, использующий функции
get
и
set
, очень некрасив по сравнению с обычным доступом на основе квадратных скобок. Однако наша цель заключается в том, чтобы начать с небольшого и простого варианта, а затем постепенно развивать его, тестируя на каждом этапе. Эта стратегия расширения и постоянного тестирования минимизирует количество ошибок и время отладки.

  • Читать дальше
  • 1
  • ...
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: