Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP.doc
Скачиваний:
7
Добавлен:
25.04.2019
Размер:
1.34 Mб
Скачать

2.3. Абстрактные классы

Поддержка механизма абстрактных классов в языке С++ преследует две цели. Во-первых, такие классы позволяют моделировать абстрактные понятия. Примером абстрактного класса может служить класс произвольных геометрических фигур CShape. У этого класса есть по меньшей мере один аспект поведения, который нельзя представить иначе как в обобщенной форме, – это изображение фигуры. Нарисовать фигуру, не зная закон ее построения, нельзя. Во-вторых, абстрактные классы могут быть использованы для описания абстрактных наборов функциональных возможностей, называемых интерфейсами. Интерфейс лишь определяет некоторый «срез» функциональности, а производные от этого интерфейса классы дают свои варианты его реализации. Примером может быть интерфейс IDispatch, который унифицирует взаимодействие объектов-клиентов и объектов-серверов в рамках технологии COM12 корпорации Microsoft. IDispatch только задает состав функций (методов) для взаимодействия, а их реализацию дает производный класс (например, в ATL13 данный интерфейс по умолчанию реализуется шаблонным классом IDispatchImpl).

Формально в С++ под абстрактным классом понимается любой класс, который содержит хотя бы одну чисто виртуальную функцию (такая функция может быть явно введена в текущем классе или унаследована от другого класса). Чисто виртуальная функция – это компонентная функция со спецификатором virtual, которая представляет абстрактный аспект поведения класса. Формат записи ее прототипа следующий:

virtual value_type function_id(parameter_list) = 0;

где value_type, function_id, parameter_list – соответственно тип значения, идентификатор и список параметров функции. Символы = 0 представляют так называемый «пустой» спецификатор (pure-specifier). Именно он сообщает компилятору о том, что функция является чисто виртуальной.

Чисто виртуальная функция декларирует некоторый аспект поведения, который можно реализовать в текущем классе лишь обобщенно. Однако он должен быть полностью реализован в одном из производных классов. Для этого производный класс переопределяет данную функцию (уже без пустого спецификатора) и определяет ее тело. Следует отметить, что если класс просто наследует чисто виртуальную функцию без определения, то он также становится абстрактным.

Использование механизма абстрактных классов требует выполнения ряда требований и ограничений. Некоторые из них даны ниже.

1. Если класс является абстрактным, то создать экземпляр этого класса можно лишь в одном случае: когда этот экземпляр есть часть объекта производного класса. Создать автономный объект абстрактного класса нельзя.

2. Чисто виртуальная функция может иметь реализацию. Реализация при этом должна быть во внешнем определении функции. Чисто виртуальный деструктор обязательно должен иметь реализацию, даже если он ничего не делает14.

3. Абстрактный класс не может специфицировать параметр функции, тип возвращаемого значения функции. Также его нельзя использовать при явном преобразовании типа.

4. Абстрактный класс может быть получен из неабстрактного класса путем введения новой, чисто виртуальной функции. Чисто виртуальная функция может «переопределять» обычную виртуальную функцию.

Ниже дан пример использования механизма абстрактных классов при построении классовой иерархии (в качестве примера выбрана уже рассмотренная ранее иерархия классов геометрических фигур, которая модифицирована с учетом возможностей абстрактных классов).

Пример

class CCenteredShape // произвольная геометрическая фигура с центром

{

public:

CCenteredShape (unsigned x = 0, unsigned y = 0): __x(x), __y(y) {}

void PrintCenter() const // печать координат центра фигуры

{

cout << xC << " " << yC << endl;

}

virtual void Draw() const = 0;

// рисование фигуры – чисто виртуальная функция

protected:

unsigned __x, __y;

};

class CFilledCenteredShape: public CCenteredShape

// закрашенная геометрическая фигура (тоже абстрактный класс)

{

public:

typedef /* ... */ fillcolor_t; // тип цвета заливки

CFilledCenteredShape(unsigned x, unsigned y, fillcolor_t c = clblack):

CCenteredShape(x,y), __fillcolor(c) {}

// функция рисование фигуры Draw не переопределяется

protected:

fillcolor_t __fillcolor; // цвет заливки

};

class CCircle: public CCenteredShape // окружность – неабстрактный класс

{

public:

CCircle(unsigned x, unsigned y, unsigned r):

CCenteredShape(x,y), __radius(r) {}

void Draw() const; // переопределение чисто виртуальной функции

double Area() const { return M_PI * __radius * __radius; }

protected:

unsigned __radius;

};

В данном примере базовым классом является CCenteredShape – класс произвольных геометрических фигур с центром. Он абстрактный, так как включает чисто виртуальную функцию Draw. У класса CCenteredShape два производных класса: первый (CFilledCenteredShape) моделирует закрашенные фигуры с центром, второй (CCircle) представляет незакрашенные окружности. Класс CFilledCenteredShape абстрактный, так как наследует от CCenteredShape чисто виртуальную функцию Draw и не обеспечивает ее переопределение без пустого спецификатора. Класс CCircle не будет абстрактным, поскольку дает функции Draw свой вариант реализации. От класса CCircle можно определять автономные объекты.

Анализируя наследование обычных, виртуальных и чисто виртуальных функций, можно сделать важные выводы, которые помогут правильно определить содержание классов при проектировании. Когда наследуется обычная (невиртуальная) компонентная функция, это означает, что производный класс таким же образом реализует соответствующий аспект поведения, как и базовый класс. Невиртуальные компонентные функции представляют собой инвариант относительно специализации классов. Например, класс CCircle наследует от базового класса CCenteredShape невиртуальную функцию PrintCenter, поскольку вывод координат центра выполняется в этих классах единообразно. Попытки переопределять невиртуальные функции не считаются компилятором ошибкой, но нарушают логику и снижают качество проекта. Когда наследуется виртуальная функция, это значит, что производный класс может реализовать соответствующий аспект поведения другим способом. Если способы реализации идентичны, то функция не переопределяется (говорят, что наследуется как интерфейс, так и необязательная реализация функции). Если же производному классу надо по-другому осуществить рассматриваемый аспект поведения, то он переопределяет функцию (или наследует только интерфейс функции, переопределяя при этом ее реализацию). И наконец, когда наследуется чисто виртуальная функция, это означает, что производный класс обязан предоставить реализацию для функции, в противном случае он также будет абстрактным (говорят, что наследуется только интерфейс функции, но у каждого неабстрактного класса свой способ реализации функции).

Чисто виртуальные функции нельзя вызывать, исключение составляет лишь следующий случай. Он относится к вызову чисто виртуальных деструкторов. Особенность механизма деструкторов состоит в том, что в классовой иерархии при уничтожении объектов должны вызываться все деструкторы, начиная деструктором текущего класса объекта до деструктора самого общего базового класса (об этом мы уже упоминали в п.2.1). Для успешного выполнения вызова деструктор должен иметь реализацию. Если деструктор чисто виртуальный, то он также снабжается пустой реализацией. Определение чисто виртуального деструктора нужно лишь для того, чтобы обеспечить уничтожение части объекта, соответствующей базовому абстрактному классу. Делать деструктор чисто виртуальным нужно не всегда, а лишь в том случае, когда текущий класс должен быть абстрактным, но выбрать функцию на роль чисто виртуальной не удается. Тогда деструктор и делается чисто виртуальным.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]