Может ли кто-нибудь пояснить какая от них практическая польза? Дело в том, что я понимаю механизм их работы, но я не понимаю для чего они нужны и где их можно использовать.
Рассмотрим на примере:
class Animal
{
public:
Animal():itsAge(1) { cout << "Animal constructor...\n"; }
virtual ~Animal() { cout << "Animal destructor...\n"; }
virtual void Speak() const { cout << "Animal speak!\n"; }
protected:
int itsAge;
};
class Dog : public Animal
{
public:
Dog() { cout << "Dog constructor...\n"; }
virtual ~Dog() { cout << "Dog destructor...\n"; }
void Speak() const { cout << "Woof!\n"; }
void WagTail() { cout << "Wagging Tail...\n"; }
}
int main()
{
Animal *pDog = new Dog;
pDog->Speak();
return 0;
}
РЕЗУЛЬТАТ:
Суть использования виртуальных функций в том, что при обращении к методу через указатель будет вызываться именно тот вариант, который был объявлен как виртуальный в базовом классе и переопределен в производном.
Но во-первых, с помощью указателя класса Animal *pDog, всё равно не получишь доступ к методу WagTail() (махать хвостом), поскольку он не был определен в классе Animal. А во-вторых, используя данный механизм придется расплатиться определенными издержками, связанными с созданием v-таблицы (каждый элемент которой занимает ресурсы оперативной памяти).
К тому же я не понимаю, зачем передавать указатель на объект производного класса, когда ожидается указатель на объект базового класса?
Обе вышеуказанные проблемы можно было бы решить объявив методы и в базовом, и в производном не виртуальными, а затем написать следующее:
Dog *pDog = new Dog;
вместо:
Animal *pDog = new Dog;
Где "профит"?
Рассмотрите классический пример с графическими формами.
Вы можете определить класс Shape
, который содержит общие методы для всех геометрических фигур, которые вы собираетесь использовать в своем приложении.
Этот класс определяет общий интерфейс для всех геометрических фигур.
И, допустим, у вас есть форма, на которой вы хотите разместить геометрические фигуры. Форма заранее не знает, какие геометрические фигуры ей придется в себя включать. Она относится к геометрическим фигурам, как к некоторым абстрактным объектам, которые наделены некоторыми методами, которые форма может использовать, чтобы вывести эти фигуры на консоль.
Так как к форме можно добавить любое количество геометрических фигур разного вида, то возникает вопрос, а как их в форме хранить? Если нет общего абстрактного представления этих фигур, то их нельзя будет хранить в форме, так как нужен один определенный тип для объектов, чтобы их всех можно было бы хранить в каком-нибудь одном контейнере и не заботиться о том, что фигуры на самом деле различны.
Это легко сделать, если наследовать все фигуры от одного класса, как в данном случае от класса Shape
, и в этом классе определить виртуальные методы, с которыми форма может работать не зависимо от того, с каким конкретным объектом форма имеет дело.
Ниже приведена простая демонстрационная программа, которая реализует описанные идеи.
Есть один класс Form
, который хранит все геометрические фигуры (в данном случае это объекты классов LeftTriangle
, RightTriangle
и Rectangle
) в стандартном контейнере std::vector
, и который имеет метод display
, позволяющий вывести все формы на консоль, делегируя каждой фигуре процесс вывода самой себя.
// Shape.cpp: определяет точку входа для консольного приложения.
//
// #include "stdafx.h"
#include <iostream>
#include <iomanip>
#include <vector>
#include <memory>
struct Point
{
int x;
int y;
};
class Shape
{
protected:
Point upper_left;
char pixel = '*';
public:
explicit Shape(Point p = { 0, 0 }) : upper_left(p)
{
}
virtual ~Shape() = default;
char set_pixel(char pixel)
{
char old_pixel = this->pixel;
this->pixel = pixel;
return old_pixel;
}
virtual std::ostream & draw(std::ostream &os = std::cout) const = 0;
Point move(int dx = 0, int dy = 0)
{
Point old_upper_left = this->upper_left;
this->upper_left.x += dx;
this->upper_left.y += dy;
if (this->upper_left.x < 0) this->upper_left.x = 0;
if (this->upper_left.y < 0) this->upper_left.y = 0;
return old_upper_left;
}
};
class Triangle : public Shape
{
protected:
unsigned int height;
public:
explicit Triangle(unsigned int height = 1) : height(height)
{
}
};
class LeftTriangle : public Triangle
{
public:
explicit LeftTriangle(unsigned int height = 1)
: Triangle(height)
{
}
std::ostream & draw(std::ostream &os = std::cout) const override
{
for (int i = 0; i < upper_left.y; i++) os << '\n';
for (unsigned int i = 0; i < height; i++)
{
os << std::setw( upper_left.x )
<< std::setfill( ' ' )
<< ""
<< std::setw(i + 2)
<< std::setfill(pixel) << '\n';
}
return os;
}
};
class RightTriangle : public Triangle
{
public:
explicit RightTriangle( unsigned int height = 1)
: Triangle( height)
{
}
std::ostream & draw(std::ostream &os = std::cout) const override
{
for (int i = 0; i < upper_left.y; i++) os << '\n';
for (unsigned int i = height; i != 0; i-- )
{
os << std::setw(upper_left.x + i - 1 )
<< std::setfill( ' ' ) << ""
<< std::setw( height - i + 2 )
<< std::setfill( pixel )
<< '\n';
}
return os;
}
};
class Rectangle : public Shape
{
protected:
unsigned int height;
unsigned int width;
public:
explicit Rectangle( unsigned int height = 1, unsigned int width = 1 )
: height(height), width( width )
{
}
std::ostream & draw(std::ostream &os = std::cout) const override
{
for (int i = 0; i < upper_left.y; i++) os << '\n';
for (unsigned int i = 0; i < height; i++)
{
os << std::setw(upper_left.x ) << std::setfill( ' ' ) << ""
<< std::setw( width + 1 ) << std::setfill(pixel)
<< '\n';
}
return os;
}
};
class Form
{
public:
Form() = default;
void add( Shape * &&shape )
{
shapes.push_back(std::unique_ptr<Shape>( shape ));
}
std::ostream & display(std::ostream &os = std::cout) const
{
const int Step = 10;
int dx = 0;
for (auto &p : shapes)
{
p->move(dx);
p->draw(os) << std::endl;
dx += Step;
}
return os;
}
private:
std::vector<std::unique_ptr<Shape>> shapes;
};
int main()
{
Form form;
form.add(new RightTriangle(5));
form.add(new LeftTriangle(5));
form.add(new Rectangle(5, 5));
form.display();
return 0;
}
Вывод программы на консоль
*
**
***
****
*****
*
**
***
****
*****
*****
*****
*****
*****
*****
Виртуальные методы определяют общий интерфейс для всех производных классов, позволяя им самим определять реализацию данного интерфейса. Чтобы можно было обращаться к объектам производных классов, как к однотипным объектам, наделенных общими свойствами, их надо привести к какому-то общему типу. Таким общем типом может быть один из общих базовых классов этих объектов. Тем самым достигается полиморфизм, то есть объекты, выглядящие как объекты одного типа, имеют множество форм поведения и представления.
Конечно каждый производный класс может дополнительно определять свои члены данных и методы. Но в таком случае это то, что различает их от объектов других производных классов.
Например, вы можете сказать, что каждая женщина и каждый мужчина, это человек. Но вы не можете сказать, например, что каждый человек - это женщина, или каждый человек - это мужчина. Если рассматривать женщин и мужчин как людей, то вы можете обращаться к ним независимо от пола, посылая им, как говорят в ООП, различные сообщения. Например, если вы - кондуктор в автобусе, то вы можете потребовать предъявить проездной билет. Для вас женщины и мужчины в автобусе - это пассажиры, и они должны иметь общие свойства такие, как наличие проездного билета. Для этого вы должны рассматривать мужчин и женщин как объектов некоторого общего типа, в данном случае, как пассажиров. Тем не менее мужчины и женщины как объекты своего индивидуального класса различаются. Например, женщины могут рожать, а мужчины не могут (если только мужчин - это не женщина, формально сменившая пол по документам).
Я не буду расписывать преимущества для всяких собачек или геометрических фигур, я выскажу одно банально звучащее соображение, но которое для моего понимания в свое время многого значило.
Когда говорят, что наследование облегчает повторное использование кода, то - о каком коде речь? О том, что производный класс использует код из базового класса? Отнюдь. Такие вещи можно делать простыми вызовами функций.
Наследование дает возможность по-новому использовать уже написанный (а то и скомпилированный в виде динамических библиотек) код.
Какая-нибудь f(Base*);
используется заново без каких-либо изменений, работая с кодом, который и близко не был написан, а может, и даже не проектировался, когда была написана и скомпилирована эта f()
- просто это код виртуальной функции в производном от Base
класса.
Да, это в определенной степени аналог передачи в функции указателей на другие функции, но только в очень определенной степени. А, кроме того, вопрос "зачем нужна передача в функции других функций?", надеюсь, не вызывает у вас недоумения "где же профит?"
Оборудование для ресторана: новинки профессиональной кухонной техники
Частный дом престарелых в Киеве: комфорт, забота и профессиональный уход
Пользовался логгером буста, но возникла потребность писать различные логи в разные файлыИ не понимаю, как его настроить так, чтобы он это...
Продолжение этого вопросаНикак не могу задать в path MapPolyLine динамически формирующиеся данные
Есть html, сконвертированный из MS Word и там используется шрифт SymbolПочему-то этот шрифт не работает в Vivaldi