Виртуальные методы и абстрактные классы
Полиморфизм обеспечивает взаимозаменяемость объектов с одинаковым интерфейсом. В С++ полиморфизм возможен только для объектов, относящихся к одной иерархии, и реализуется с помощью виртуальных методов. Те методы, которые могут быть переопределены в производных классах, должны быть объявлены в базовом классе с помощью спецификатора
virtual. При переопределении метод должен иметь то же имя и набор параметров. Спецификатор
virtual при переопределении метода в производных классах можно не указывать. Допускается изменять тип возвращаемого результата – вместо ссылки или указателя на базовый класс вернуть ссылку или указатель на производный класс. Переопределить виртуальный метод можно в сколь угодно дальнем потомке и сколько угодно раз. Если метод в каком-то классе этой иерархии не переопределяется, то используется определение из базового класса этого класса. При перегрузке виртуальных методов рекомендуется указать спецификатор
override после заголовка.
Статический метод нельзя объявить виртуальным.
При вызове виртуального метода вызывается его реализация из реального класса объекта, хотя объект может быть представлен ссылкой или указателем на базовый класс. Если необходимо вызвать реализацию виртуального метода из базового класса, то перед именем метода нужно написать имя базового класса и
::.
Если определить виртуальный метод в базовом классе нельзя, то после заголовка вместо тела метода нужно указать =0. Такой метод называется
чисто виртуальным, а класс, содержащий хотя бы один такой метод, –
абстрактным. Нельзя создавать объекты абстрактных классов, но можно работать со ссылками или указателями на абстрактные классы. В производном классе все чисто виртуальные методы должны быть определены, чтобы класс перестал быть абстрактным.
class Shape {
public:
virtual double square()=0; //
};
class Rectangle: public Shape
{ double w,h;
public:
Rectangle(double w, double h):w(w),h(h){}
double square() { return w*h; } //
};
class Circle: public Shape
{ double r;
public:
Circle(double r):r(r){}
double square() { return r*r*acos(0.)*2; } //
};
int main()
{ Shape *s=new Circle(10);
cout<<(s->square())<<"\n"; //
}
Чисто виртуальному методу в абстрактном классе можно дать определение, которое можно вызвать по имени класса.
class Figure {
int color, x, y;
public:
Figure(int color, int x, int y):color(color),x(x),y(y) {}
virtual void draw()=0; //
};
void Figure::draw() //
{ setcolor(color);
moveto(x,y);
}
class Rectangle: public Figure
{ int w,h;
public:
Rectangle(int color, int x, int y, int w, int h)
:Figure(color,x,y),w(w),h(h){}
void draw() override; //
};
void Rectangle::draw()
{ Figure::draw(); //
linerel(w,0);
linerel(0,h);
linerel(-w,0);
linerel(0,-h);
}
int main()
{ Figure *s=new Rectangle(RED,10,20,100,50);
...
s->draw(); //
}
При перегрузке виртуальных методов рекомендуется указать спецификатор
override. Это позволит компилятору найти ошибку в случае неверной сигнатуры виртуального метода в производном классе.
Cпецификатор
final используется, чтобы запретить дальнейшую перегрузку виртуального метода или наследование от класса.
struct A {
virtual void f(int);
virtual void g(double);
};
struct B: A {
void f(int) override final; //
void g(int) override; //
};
struct C final: B {
void f(int) override; //
};
struct D final: A {...};
struct E: D { ... }; //