
- •История
- •Развитие и стандартизация языка
- •Совместимость с языком с
- •Обзор языка
- •Полиморфизм
- •Инкапсуляция
- •Конструкторы и деструкторы
- •Средства c, которых рекомендуется избегать
- •Пример № 5
- •Достоинства
- •Критика
- •Синтаксис
- •Ограниченность возможностей
- •Избыточные и опасные возможности
- •Вычислительная производительность
- •Результативность
- •Влияние и альтернативы
Полиморфизм
Семантика системы типов С++ не полиморфна (в отличие от потомков ML, в том числе гибридных с Си — BitC, Cyclone), однако есть несколько способов обеспечить полиморфное поведение. Прежде всего это перегрузка методов классов при наследовании — традиционный для ООП способ обеспечения абстракции данных. Затем есть два способа реализации параметрического полиморфизма (в С++-сообществе обычно называемого «обобщённым программированием»). Первый способ наследован из Си — использование бестипового указателя и приведение типа в зависимости от других данных — хотя в С++ этот способ традиционно считается неидеоматичным и опасным. Второй заключается в использовании шаблонов — но, в отличие от обычных реализаций параметрического полиморфизма, в С++ происходит автоматическая генерация семейства перегруженных мономорфных функций на основании полиморфного определения (шаблона) в соответствии с контекстами его использования — то есть параметрический полиморфизм на уровне исходного кода транслируется в ситуативный (ad hoc) на уровне машинного, за что С++ подвергается критике (см. раздел Вычислительная производительность). В С++ также есть третий вид перегрузки — Перегрузка операторов — которая в сочетании с наследованием классов предоставляет ещё большие возможности повышения читабельности кода путём ввода т. н. «синтаксического сахара».
Для обеспечения абстракции данных необходимо связать несколько классов в иерархию наследования и назначить функциям одинаковые спецификации. Например:
class Figure
{
...
void Draw() const;
...
};
class Square : public Figure
{
...
void Draw() const;
...
};
class Circle : public Figure
{
...
void Draw() const;
...
};
class Window
{
...
void Draw() const;
...
};
class SquareWindow : public Window
{
...
void Draw() const;
...
};
class RoundWindow : public Window
{
...
void Draw() const;
...
};
В результате компиляции этих определений формируется шесть тел функций. В коде они используются одинаково; выбор конкретного экземляра функции осуществляется в зависимости от типа экземпляра объекта, для которого осуществляется вызов. Согласованность поведения функций остаётся на совести программиста.
Circle *c = new Circle(0,0,5);
Figure *f = c; // правильно: Figure — базовый класс для Circle
c->Draw();
f->Draw(); // Указатели равны друг другу, но для f и c будут вызваны разные функции
SquareWindow *sw = new SquareWindow(0,0,5);
sw->Draw(); // используется так же
f = sw; // ошибка! SquareWindow не входит в число наследников Figure!
Как видно, диапазон этого вида полиморфизма в С++ ограничивается на этапе проектирования заданным перечнем типов. По умолчанию такой полиморфизм является статическим, но при использовании спецификатора virtual он превращается в динамический (см. позднее связывание):
class Figure
{
...
virtual void Draw() const;
...
};
class Square : public Figure
{
...
void Draw() const;
...
};
class Circle : public Figure
{
...
void Draw() const;
...
};
Figure* figures[10];
figures[0] = new Square(1, 2, 10);
figures[1] = new Circle(3, 5, 8);
...
for (int i = 0; i < 10; i++)
figures[i]->Draw();
В этом случае для каждого элемента массива будет вызвана Square::Draw() или Circle::Draw(), в зависимости от вида фигуры.
Чистой виртуальной функцией называется виртуальная функция-член, которая объявлена со спецификатором = 0 вместо тела:
class Figure
{
...
virtual void Draw() const = 0;
);
Чистая виртуальная функция не имеет определения и не может быть непосредственно вызвана. Цель объявления такой функции — создать в общем базовом классе сигнатуру-прототип, которая не имела бы собственного определения, но позволяла создавать такие определения в классах-потомках и вызывать их через указатель на общий базовый класс. Функция-член объявляется чистой виртуальной тогда, когда её определение для базового класса не имеет смысла. Так, в вышеприведённом примере для базового класса Figure определение функции Draw() не имеет смысла, так как «фигур вообще» не бывает и их невозможно отобразить, но описание такой функции необходимо, чтобы можно было её переопределить в классах-потомках и вызывать методы Draw этих классов через указатель на Figure. Следовательно, вполне логично объявить Figure.Draw() как чистую виртуальную функцию-член.
С понятием чистой виртуальной функции в C++ тесно связано понятие «абстрактный класс». Абстрактным классом называется такой, у которого есть хотя бы одна не переопределённая чистая виртуальная функция-член. Экземпляры таких классов создавать запрещено, абстрактные классы могут использоваться только для порождения новых классов путём наследования. Если в классе-потомке абстрактного класса не переопределены все унаследованные чистые виртуальные функции, то такой класс также является абстрактным и на него распространяются все указанные ограничения.
Абстрактные классы часто используются как интерфейсы. В отличие от чистых интерфейсов других языков, абстрактные классы С++ могут иметь невиртуальные функции и члены-данные.