Статический вызов виртуальной функции
Вызывая виртуальную функцию с помощью оператора разрешения области видимости класса, мы отменяем механизм виртуализации и разрешаем вызов статически, на этапе компиляции. Предположим, что мы определили виртуальную функцию isA() в базовом и каждом из производных классов иерархии Query:
Query *pquery = new NameQuery( "dumbo" ); // isA() вызывается динамически с помощью механизма виртуализации // реально будет вызвана NameQuery::isA() pquery->isA(); // isA вызывается статически во время компиляции // реально будет вызвана Query::isA |
pquery->Query::isA();
Тогда явный вызов Query::isA()
разрешается на этапе компиляции в пользу реализации isA() в базовом классе Query, хотя pquery
адресует объект NameQuery.
Зачем нужно отменять механизм виртуализации? Как правило, ради эффективности. В теле виртуальной функции производного класса часто необходимо вызвать реализацию из базового, чтобы завершить операцию, расщепленную между базовым и производным классами. К примеру, вполне вероятно, что виртуальная функция display() из Camera
выводит некоторую информацию, общую для всех камер, а реализация display() в классе PerspectiveCamera
сообщает информацию, специфичную только для перспективных камер. Вместо того чтобы дублировать в ней действия, общие для всех камер, можно вызвать реализацию из класса Camera. Мы точно знаем, какая именно реализация нам нужна, поэтому нет нужды прибегать к механизму виртуализации. Более того, реализация в Camera
объявлена встроенной, так что разрешение во время компиляции приводит к подстановке по месту вызова.
Приведем еще один пример, когда отмена механизма виртуализации может оказаться полезной, а заодно познакомимся с неким аспектом чисто виртуальных функций, который начинающим программистам кажется противоречащим интуиции.
Реализации функции print() в классах AndQuery и OrQuery
совпадают во всем, кроме литеральной строки, представляющей название оператора. Реализуем только одну функцию, которую можно вызывать из данных классов. Для этого мы снова определим абстрактный базовый BinaryQuery (его наследники – AndQuery и OrQuery). В нем определены два операнда и еще один член типа string для хранения значения оператора. Поскольку это абстрактный класс, объявим print()
чисто виртуальной функцией:
class BinaryQuery : public Query { public: BinaryQuery( Query *lop, Query *rop, string oper ) : _lop(lop), _rop(rop), _oper(oper) {} ~BinaryQuery() { delete _lop; delete _rop; } ostream &print( ostream&=cout, ) const = 0; protected: Query *_lop; Query *_rop; string _oper; |
Вот как реализована в BinaryQuery
функция print(), которая будет вызываться из производных классов AndQuery и OrQuery:
inline ostream& BinaryQuery:: print( ostream &os ) const { if ( _lparen ) print_lparen( _lparen, os ); _lop->print( os ); os << ' ' << _oper << ' '; _rop->print( os ); if ( _rparen ) print_rparen( _rparen, os ); return os; |
Похоже, мы попали в парадоксальную ситуацию. С одной стороны, необходимо объявить этот экземпляр print() как чисто виртуальную функцию, чтобы компилятор воспринимал BinaryQuery как абстрактный базовый класс. Тогда в приложении определить независимые объекты BinaryQuery
будет невозможно.
С другой стороны, нужно определить в классе BinaryQuery виртуальную функцию print() и уметь вызывать ее через объекты AndQuery и OrQuery.
Но как часто бывает с кажущимися парадоксами, мы не учли одного обстоятельства: чисто виртуальную функцию нельзя вызывать с помощью механизма виртуализации, но можно вызывать статически:
inline ostream& AndQuery:: print( ostream &os ) const { // правильно: подавить механизм виртуализации // вызвать BinaryQuery::print статически BinaryQuery::print( os ); |