Что такое чистая виртуальная функция c
Перейти к содержимому

Что такое чистая виртуальная функция c

  • автор:

Что такое чистая виртуальная функция c

Иногда возникает необходимость определить класс, который не предполагает создания конкретных объектов. Например, класс фигуры. В реальности есть конкретные фигуры: квадрат, прямоугольник, треугольник, круг и так далее. Однако абстрактной фигуры самой по себе не существует. В то же время может потребоваться определить для всех фигур какой-то общий класс, который будет содержать общую для всех функциональность. И для описания подобных сущностей используются абстрактные классы.

Абстрактные классы — это классы, которые содержат или наследуют без переопределения хотя бы одну чистую виртуальную функцию. Абстрактный класс определяет интерфейс для переопределения производными классами.

Что такое чистые виртуальные функции (pure virtual functions)? Это функции, которые не имеют определения. Цель подобных функций — просто определить функционал без реализации, а реализацию определят производные классы. Чтобы определить виртуальную функцию как чистую, ее объявление завершается значением «=0». Например, определим абстрактный класс, который представляет геометрическую фигуру:

class Shape < public: virtual double getSquare() const = 0; // площадь фигуры virtual double getPerimeter() const = 0; // периметр фигуры >;

Класс Shape является абстрактным, потому что он содержит как минимум одну чистую виртуальную функцию. А в данном случае даже две таких функции — для вычисления площади и периметра фигуры. И ни одна из функций не имеет никакой реализации. В данном случае обе функции являются константными, но это необязательно. Гловно, чтобы любой производный класс от Shape должен будет предоставить для этих функций свою реализацию.

При этом мы не можем создать объект абстрактного класса:

Shape shape<>;

Для применения абстрактного класса определим следующую программу:

#include class Shape < public: virtual double getSquare() const = 0; // площадь фигуры virtual double getPerimeter() const = 0; // периметр фигуры >; class Rectangle : public Shape // класс прямоугольника < public: Rectangle(double w, double h) : width(w), height(h) < >double getSquare() const override < return width * height; >double getPerimeter() const override < return width * 2 + height * 2; >private: double width; // ширина double height; // высота >; class Circle : public Shape // круг < public: Circle(double r) : radius(r) < >double getSquare() const override < return radius * radius * 3.14; >double getPerimeter() const override < return 2 * 3.14 * radius; >private: double radius; // радиус круга >; int main() < Rectangle rect; Circle circle; std::cout

Здесь определены два класса-наследника от абстрактного класса Shape — Rectangle (прямоугольник) и Circle (круг). При создании классов-наследников все они должны либо определить для чистых виртуальных функций конкретную реализацию, либо повторить объявление чистой виртуальной функции. Во втором случае производные классы также будут абстрактными.

В данном же случае и Circle, и Rectangle являются конкретными классами и реализуют все виртуальные функции.

Консольный вывод программы:

Rectangle square: 1500 Rectangle perimeter: 160 Circle square: 2826 Circle perimeter: 188.4

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

#include class Shape < public: Shape(int x, int y): x, y <> virtual double getSquare() const = 0; // площадь фигуры virtual double getPerimeter() const = 0; // периметр фигуры void printCoords() const < std::cout private: int x; int y; >; class Rectangle : public Shape // класс прямоугольника < public: Rectangle(int x, int y, double w, double h) : Shape, width(w), height(h) < >double getSquare() const override < return width * height; >double getPerimeter() const override < return width * 2 + height * 2; >private: double width; // ширина double height; // высота >; class Circle : public Shape // круг < public: Circle(int x, int y, double r) : Shape, radius(r) < >double getSquare() const override < return radius * radius * 3.14; >double getPerimeter() const override < return 2 * 3.14 * radius; >private: double radius; // радиус круга >; int main() < Rectangle rect; rect.printCoords(); // X: 0 Y: 0 Circle circle; circle.printCoords(); // X: 10 Y: 20 >

В данном случае класс Shape также имеет две переменных, конструктор, который устанавливает их значения, и невиртуальную функцию, которая выводит их значения. В производных классах также необходимо вызвать этот конструктор. Однако объект абстрактного класса с помощью его конструктора мы создать не можем.

Что такое чистая виртуальная функция c

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

Чистые виртуальные функции

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

class A
public:
virtual void v_function(void)=0;//чистая виртуальная функция
>;

Как видите, все отличие только в том, что появилась конструкция =0, которая называется чистый спецификатор. Чистая виртуальная функция абсолютно ничего не делает и недоступна для вызовов. Ее назначение служить основой (если хотите, шаблоном) для замещающих функций в производных классах. Класс, который содержит хотя бы одну чистую виртуальную функцию, называется абстрактным классом. Почему абстрактным? Потому, что создавать самостоятельные объекты такого класса нельзя. Это всего лишь заготовка для других классов. Механизм абстрактных классов разработан для представления общих понятий, которые в дальнейшем предполагается конкретизировать. Эти общие понятия обычно невозможно использовать непосредственно, но на их основе можно, как на базе, построить производные частные классы, пригодные для описания конкретных объектов.
Пример? Пожалуйста.

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

Простейшая программа

Возвращаясь к воинственному примеру из первой части, надо сказать, что в этом случае это абсолютно миролюбивый и даже детский пример. Кстати, не смотря на всю его простоту, он вполне может быть основой для создания простейшей развивающей игры для детей очень младшего возраста.
Итак. В основу положена идея об ознакомлении с животным миром. (Круто заявлено) Ни для кого не секрет (я надеюсь), что все животные издают звуки. Причем разные и весьма характерные для своего вида. На этом и сыграем.
Для упрощения примера ограничимся в описании каждого животного его кличкой и типовым издаваемым животным звуком. Ну, а основной (и увы, единственной) возможностью программы будет вывод на экран списка кличек животных и представления издаваемых ими звуков.

//абстрактный базовый класс
class Animal
public:
char *Title; //кличка животного
Animal(char *t) //простой конструктор
virtual void speak(void)=0; //чистая виртуальная функция
>;

//класс лягушка
class Frog: public Animal
public:
Frog(char *Title): Animal(Title) < >;
virtual void speak(void) < cout;
>;

//класс собака
class Dog: public Animal
public:
Dog(char *Title): Animal(Title) < >;
virtual void speak(void) < cout;
>;

//класс кошка
class Cat: public Animal
public:
Cat(char *Title): Animal(Title) < >;
virtual void speak(void) < cout;
>;

int main ()
//объявим массив указателей на базовый класс Animal
//и сразу его заполним указателями, создавая объекты
Animal *animals[4] = < new Dog("Бобик"),
new Cat(«Мурка»),
new Frog(«Кермит»),
new Lion(«Кинг»)>; // cписок животных

В качестве базового класса мы соорудили абстрактный класс Animal. Он имеет единственный член-данные Title, описывающий кличку животного. В нем есть явно определенный конструктор, который присваивает животному его имя. И единственная чистая виртуальная функция speak(), которая описывает, какие звуки издает животное.
Из этого класса выведены все остальные. Кроме одного. Класс лев порожден от класса кошка (ведь львы это тоже кошки!). Это сделано для демонстрации тонкостей применения виртуальных функций. Но об этом классе немного позже. А сейчас как работает программа.
Во всех производных классах описана собственная замещающая виртуальная функция speak(), которая печатает на экран, какие же звуки издает конкретное животное.
В основном теле программы объявлен массив animals[4] указателей типа Animal*. И сразу же созданы динамические объекты классов и заполнен массив указателей. А в цикле for() по указателю просто вызывается виртуальная функция speak().
Если вы не сделали никаких новых ошибок при вводе программы, то вывод на экран должен выглядеть так:

Бобик говорит гав-гав
Мурка говорит мяу-мяу
Кермит говорит ква-ква
Кинг говорит ррр-ррр

Все работает. Каждый объект сам выводит свою запись. Виртуальные функции действуют!
А теперь вернемся к описанию класса Lion (лев).
В нем вместо одной виртуальной функции speak() содержится сразу три. Правда две из них закомментированы. Если вы закомментируете первую функцию, а раскомментируете вторую, то сможете проверить вариант, когда производится попытка соорудить виртуальную замещающую функцию с другим типом возвращаемого значения. В данном случае вторая (неправильная) функция возвращает тип int вместо типа void, который был у функции speak()в базовом классе. Попробуйте скомпилировать программу компилятор сразу же предъявит вам претензии по поводу:

Error: animals.cpp(42,25):Virtual function ‘Lion::speak()’ conflicts with base class ‘Cat’

А система помощи из Borland C++ 5 выдаст следующий хелп:

A virtual function has the same argument types as one in a base class, but a different return type. This is illegal.
То есть виртуальная функция имеет тот же аргумент, что и в базовом классе, но возвращает другой тип. Это недопустимо.

Дальше еще интереснее. Попробуйте раскомментировать третью функцию, а первые две закомментируйте. Компилятор на сей раз просто выдаст предупреждение (но не ошибку, и программа будет работать!):

Warn : animals.cpp(44,3):’Lion::speak(int)’ hides virtual function ‘Cat::speak()’

Это тот самый случай, когда объявляется замещающая виртуальная функция с тем же самым типом возвращаемого значения, но с другим набором параметров. Что в случае со звуками, которые издает лев может быть передано функции speak(), как параметр? Предположение типа каким местом издается звук я отмел сразу же и бесповоротно. Предположим, что это зависимость от времени суток, то есть когда. Ну, например, ближе к ночи лев захотел спать, и стал зевать. Поэтому в данном случае функции speak(int When)передан параметр When, который правда в ней нигде не используется, но это не важно. Функция-то все равно работать будет.
Ну, раз программа скомпилировалась, надо запустить ее. Что получилось? Должно быть следующее:

Бобик говорит гав-гав
Мурка говорит мяу-мяу
Кермит говорит ква-ква
Кинг говорит мяу-мяу

Оба-на! Кажется что-то не так! Лев-то у вас уже не рычит и не зевает, а мило мяукает. С чего бы это? А ведь компилятор предупреждал функция ‘Lion::speak(int)’ скрывает (переопределяет) виртуальную функцию ‘Cat::speak()’. Это уже совсем другая функция! Поэтому, раз в данном классе нет правильно определенной виртуальной функции, то по указателю вызывается виртуальная функция speak()из базового класса. А в нашем случае базовым для класса Lion является класс Cat. Вот лев у вас и замяукал!

Вот, пока что и все. Рассмотрение теории виртуальных функций в общем случае мы закончили. Правда мы не рассмотрели еще много разных моментов. Но это уже очень тонкие тонкости. Об этом можно целую книгу написать.
В следующей части мы попытаемся рассмотреть несколько более сложный момент. Посмотрим, что такое виртуальные деструкторы классов, зачем они нужны, и как с ними бороться.

При написании этого материала использовались следующие книги:

Tom Swan Mastering Borland C++ 5
Перевод Диалектика, Киев, 1996

В.В. Подбельский Язык С++ 5-е издание
Издание Москва, Финансы и статистика, 2001

Paul Kimmel Using Borland C++ 5 Special Edition
Перевод BHV Санкт-Петербург, 1997

Если у вас есть вопросы пишите, будем разбираться.

Чистые виртуальные методы

Как использовать чистые виртуальные методы? Где их обычно используют, в каких случаях, какие особенности?

Отслеживать
67.9k 216 216 золотых знаков 77 77 серебряных знаков 219 219 бронзовых знаков
задан 30 янв 2016 в 13:21
1,993 3 3 золотых знака 16 16 серебряных знаков 34 34 бронзовых знака

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

Чистые виртуальные функции делают класс абстрактным. Это дает гарантию того, что метод будет переопределен в одном из классов-наследников, иначе код, в котором выполняется попытка создания экземпляра такого класса, не скомпилируется.

struct A < virtual void f() = 0; >; struct B : A <>; struct C : B < void f() override <>// переопределение f >; struct D : C <>; A a; // ошибка: f - чистая виртуальная, A - абстрактный класс B b; // ошибка: f - чистая виртуальная и не была переопределена, B - абстрактный класс C c; // ОК: f была переопределена D d; // ОК: f была переопределена в С 

Также, чистые виртуальные функции позволяют не писать тело метода, это полезно когда только классы-потомки знают что должен делать метод, тогда в базовом классе можно просто ничего не писать:

enum Color < Red, Green, Blue >; struct HasColor < // плохо - непонятно почему по умолчанию должен быть именно красный // virtual Color get_color() < return Red; >// ок, просто не пишем тело функции virtual Color get_color() = 0; >; 

При этом чистые виртуальные функции могут иметь тело.
Такая функция по-прежнему требует переопределения в наследнике, но в теле функции можно указать какое-то поведение по умолчанию.

struct Base < virtual void f() = 0; >; void Base::f() <> // тело чисто виртуальной функции struct Derived : Base < void f() override < Base::f(); // вызываем функцию базового класса. >>; 

Отслеживать
28.5k 12 12 золотых знаков 58 58 серебряных знаков 118 118 бронзовых знаков
ответ дан 30 янв 2016 в 13:44
30.9k 13 13 золотых знаков 96 96 серебряных знаков 157 157 бронзовых знаков

Их используют в базовых классах для планирования архитектуры потомков. Например мы создаем класс animal(животные) и от него будем создавать классы животных в каждом животном будет метод move(двигаться), но каждое животное может двигаться на разные дистанции и одинаковая реализация этого метода в базовом классе нас не устроит. Поэтому мы объявляем метод move как виртуальный и переопределяем его в классах потомках. Поскольку этот метод виртуальный он лишь создает общий базовый интерфейс для всех подклассов. В будущем это нам поможет делать приведение типов.

Обновил ответ. Пример использования абстрактного интерфейса

#include using namespace std; class Animal< public: virtual void move() = 0; // теперь мы знаем что этот метод будет полностью реализован в потомке >; class Monkey: public Animal < public: void move() < cout // реализовали >; class Puma: public Animal < public: void move() < cout // реализовали >; int main()< Animal * a; // указатель на базовый класс a = new Monkey; // создали дочерний класс обезьяны a->move(); // вызвали метод из класса обезьяны delete(a); a = new Puma; // создали дочерний класс пумы a->move(); // вызвали метод из пумы delete(a); return 0; > 

Виртуальные функции

Виртуальные функции — специальный вид функций-членов класса. Виртуальная функция отличается об обычной функции тем, что для обычной функции связывание вызова функции с ее определением осуществляется на этапе компиляции. Для виртуальных функций это происходит во время выполнения программы.

Для объявления виртуальной функции используется ключевое слово virtual . Функция-член класса может быть объявлена как виртуальная, если

  • класс, содержащий виртуальную функцию, базовый в иерархии порождения;
  • реализация функции зависит от класса и будет различной в каждом порожденном классе.

Виртуальная функция — это функция, которая определяется в базовом классе, а любой порожденный класс может ее переопределить. Виртуальная функция вызывается только через указатель или ссылку на базовый класс.

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

Указатель на базовый класс может указывать либо на объект базового класса, либо на объект порожденного класса. Выбор функции-члена зависит от того, на объект какого класса при выполнении программы указывает указатель, но не от типа указателя. При отсутствии члена порожденного класса по умолчанию используется виртуальная функция базового класса.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

#include
using namespace std;
class X
protected :
int i;
public :
void seti( int c) < i = c; >
virtual void print() < cout
>;
class Y : public X // наследование
public :
void print() < cout // переопределение базовой функции
>;
int main()
X x;
X *px = &x; // Указатель на базовый класс
Y y;
x.seti(10);
y.seti(15);
px->print(); // класс X: 10
px = &y;
px->print(); // класс Y: 15
cin.get();
return 0;
>

Виртуальная функция

Результат выполнения

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

Без виртуальной функции

Если в строке 9 (см. код выше) убрать ключевое слово virtual , то результат выполнения будет уже другим, т.к. связывание функций будет происходить на этапе компиляции:

В терминологии ООП «объект посылает сообщение print и выбирает свою собственную версию соответствующего метода». Виртуальной может быть только нестатическая функция-член класса. Для порожденного класса функция автоматически становится виртуальной, поэтому ключевое слово virtual можно опустить.

Пример : выбор виртуальной функции

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

#include
using namespace std;
class figure
protected :
double x, y;
public :
figure( double a = 0, double b = 0) < x = a; y = b; >
virtual double area() < return (0); >// по умолчанию
>;
class rectangle : public figure
public :
rectangle( double a = 0, double b = 0) : figure(a, b) <>;
double area() < return (x*y); >
>;
class circle : public figure
public :
circle( double a = 0) : figure(a, 0) <>;
double area() < return (3.1415*x*x); >
>;
int main()
figure *f[2];
rectangle rect(3, 4);
circle cir(2);
double total = 0;
f[0] = ▭
f[1] = ○
total = f[1]->area();
cout total += f[0]->area();
cout cin.get();
return 0;
>

Переопределение виртуальной функции

Результат выполнения

Чистая виртуальная функция

Базовый класс иерархии типа обычно содержит ряд виртуальных функций, которые обеспечивают динамическую типизацию. Часто в самом базовом классе сами виртуальные функции фиктивны и имеют пустое тело. Определенное значение им придается лишь в порожденных классах. Такие функции называются чистыми виртуальными функциями .

Чистая виртуальная функция — это метод класса, тело которого не определено.

В базовом классе такая функция записывается следующим образом:

virtual ПрототипФункции = 0;
virtual void func() = 0;

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

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

virtual double area() = 0;

Комментариев к записи: 4

Спасибо очень, чётко и просто объяснили в отличие от других сайтов, где авторы статей, словно школьники, которых поймали, когда те курили за гаражами, уходящие от ответа на вопрос: «Зачем?».

Нормальная статья. Вирутуальная функция — это просто указатель на функцию, которую каждый класс потомок может изменить на свою. Это если бы мы писали на С

Fruit Ninja Online

Виртуальные методы — один из важнейших приёмов реализации полиморфизма . Они позволяют создавать общий код, который может работать как с объектами базового класса, так и с объектами любого его класса-наследника. При этом базовый класс определяет способ работы с объектами и любые его наследники могут предоставлять конкретную реализацию этого способа.

Сюда же можно было бы про виртуальный деструктор написать, я думаю. Статья годная, первый пример не очень удачный. С абстрактными классами можно было бы связать второй пример. У Вас там как раз описывается некая «фигура», т.е. тоже абстрактное понятие, для которого нельзя описать метод вычисления площади или чего-то там. Это был бы удачный пример, т.к. это:

virtual double area() < return (0);>// по умолчанию

не совсем верно. Формально, фигура — это множество точек и оно может не иметь площади, возвращать ноль — не правильно. Описать чисто виртуальный метод — правильно. Термин «отсроченный метод» какой-то странный. Он встречается в классических книгах?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *