printНаследование

printВиртуальные методы и абстрактные классы

Полиморфизм обеспечивает взаимозаменяемость объектов с одинаковым интерфейсом. В С++ полиморфизм возможен только для объектов, относящихся к одной иерархии, и реализуется с помощью виртуальных методов. Те методы, которые могут быть переопределены в производных классах, должны быть объявлены в базовом классе с помощью спецификатора 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; // OK
  void g(int) override; // Error
};
struct C final: B {
  void f(int) override; // Error
};
struct D final: A {...};
struct E: D { ... }; // Error
loading