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


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

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



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

 

virtual  void  FF(...) = 0;

 

Такая функция называется чистой виртуальной функцией. Если в классе задана чистая виртуальная функция, то определить объект этого класса нельзя!

Например, задан класс:

 

class A

{ public:

virtual   Print() = 0;  // чистая виртуальная функция

...

};

 

Тогда следующий фрагмент – ошибочен:

 

{ A  x;                       // ошибка!

...

}

 

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

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

 

A       Func(B x, A y){..}

         

Ошибка!    Ошибка!

5.4. Правила определения виртуальных функций

Правило 1. Виртуальная функция в базовом классе либо определяется, либо объявляется как чистая виртуальная;

 

Правило 2. Виртуальная функция в порожденном классе:

а) либо определяется,

б) либо заново объявляется как чистая виртуальная,

в) либо остается таковой по умолчанию.

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

virtual  void  FF(..) { }

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

 

Правило 4. Базовому классу известны все переопределения его виртуальных функций, однако базовому классу неизвестны виртуальные функции, объявленные в порожденных классах.

Рассмотрим это свойство на следующем примере. Например, заданы классы

 

class A

 { …

public:

virtual  void  Print() = 0; // чистая виртуальная

 };

 

class  B: public A

{...

public:

virtual  void  Print() = 0; // осталась и в B чистой виртуальной

virtual  int  F() = 0;        // новая чистая виртуальная функция

...

};

 

class  C: public B

{...

public:

virtual  int  F() {return 3;}

virtual  void  Print() {cout << “\nКласс C:”;}

};

 

void  main()                // Что можно, что нельзя?

{A  *pa; C  z; C  *pc = &z; B  *pb;

pa = pc;

pa –> Print();   // можно: работает C::Print(), объявленная в A, как чистая виртуальная

int  k = pa –> F(); // так нельзя: F() не объявлялась в базовом классе A  никак!

pb = pc;

k = pb –> F(); // верно: именно в классе B, как базовом для C, F() была объявлена как виртуальная (чистая)

}

 

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

 

а) виртуальная функция вызывается не через указатель или ссылку, а через объект.

Например,

 

B  y;

y.Print(); // Ясно: B::Print()

 

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

 

A  x; B  y; A  *pa = &y;

pa –> B:: Print(); // Невиртуальный вызов виртуальной функции

pa –> Print();     // Виртуальный вызов виртуальной функции

 

в) Если виртуальная функция вызывается в конструкторе или деструкторе порожденного класса, то всегда вызывается функция базового класса, т.к. объект порожденного класса еще не создан или уже разрушен.

 

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

Например, определена функция

 

void  DoPrint(A  &a)

{a.Print();        // Виртуальный вызов функции Print

}

 

и в основной функции задан вызов ее с объектами разных классов:

 

void  main()

{A  x(1, 1, 1); B  y(2, 3, 1, 6, 4, 7);

DoPrint(x);      // Выполнится  A:: Print();

DoPrint(y); // Выполнится  B:: Print();

}

 

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

Например, в классе A определим виртуальную операцию ‘-‘ (изменить знак данных)

 

class  A

{....

public:

virtual  void  operator -()

{d1 = -d1; d2 = -d2; d3 = -d3;}

....

};

 

class  B: public  A

{...

public:

void operator-()

{ // в том случае, если d1 будет в части protected можно и так:

// d1= -d1;d2= -d2;A::d3= -A::d3;d3= -d3;d4= -d4;

// то есть в общем случае нельзя.

// Но короче и грамотнее так:

 A:: operator-(); // работает базовая операция ‘-‘. Без A:: будет рекурсия

 d3 = -d3; d4 = -d4;}

};

 

Примеры виртуальных вызовов операции ‘-‘:

 

A  x(2, 2, 3), *pa;

B  y(5, 4, 3, 2, 1);

pa = &y;

-(*pa); // виртуальный вызов операции ‘-’ через указатель на базовый класс для объекта y

pa –> Print(); // виртуальный вызов B::Print()

A &sa = y;

-sa;        // виртуальный вызов операции ‘-‘ через ссылку sa

sa.Print();  //виртуальный вызов функции Print() через ссылку

 

Правило 8. Виртуальные функции могут быть и приватными.

Например, функция print() в классах A и B:

 

class A 

{virtual  void  print() {cout << “\n********A*********\n”;}

  protected:  int  a;

public:  

A()  {a = 1;}

virtual   void  Print() {print(); cout << a;}

...

};

 

class  B: public  A

{ int b;

void  print() {cout << “\n**********B********\n”;}

public;

B() {b = 2;}

void  Print() {print(); cout << a << ’ ‘ << b;}

};

 

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

 

A  x, *pa; B  y;

pa = &x;

pa –> Print(); // Работают функции Print() и print() класса A

pa = &y;

pa –> Print(); // Работают функции Print() и print() класса B

 

Замечание. Удивительно другое. Если бы функция Print() класса B была задана так:

 

B:: Print() {print();

               A:: Print();

               cout << ’ ‘ << b;

}

 

то при виртуальном вызове

 

pa = &y;

pa –> Print();

 

2 раза проработала бы виртуальная функция print() из класса B.

Почему? Понять можно, разобравшись с механизмом позднего связывания.

 



Поделиться:


Последнее изменение этой страницы: 2021-12-07; просмотров: 47; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.141.199.243 (0.014 с.)