Выбрать главу

NameQuery nq( "lilacs" );

// правильно: но nq "усечено" до подобъекта Query

Query qobject = nq;

Инициализация qobject переменной nq абсолютно законна: теперь qobject равняется подобъекту nq, который соответствует базовому классу Query, однако qobject не является объектом NameQuery. Часть nq, принадлежащая NameQuery, “усечена” перед инициализацией qobject, поскольку она не помещается в область памяти, отведенную под объект Query. Для поддержки этой парадигмы приходится использовать указатели и ссылки, но не сами объекты:

void print ( Query object,

const Query *pointer,

const Query &reference )

{

// до момента выполнения невозможно определить,

// какой экземпляр print() вызывается

pointer-print();

reference.print();

// всегда вызывается Query::print()

object.print();

}

int main()

{

NameQuery firebird( " firebird" );

print( firebird, &firebird, firebird );

}

В данном примере оба обращения через указатель pointer и ссылку reference разрешаются своим динамическим типом; в обоих случаях вызывается NameQuery::print(). Обращение же через объект object всегда приводит к вызову Query::print(). (Пример программы, в которой используется эффект "усечения", приведен в разделе 18.6.2.)

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

17.5.1. Виртуальный ввод/вывод

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

ostream& print( ostream &os = cout ) const;

Функцию print() следует объявить виртуальной, поскольку ее реализации зависят от типа, но нам нужно вызывать ее через указатель типа Query*. Например, для класса AndQuery эта функция могла бы выглядеть так:

ostream&

AndQuery::print( ostream &os ) const

{

_lop-print( os );

os " && " ;

_rop-print( os );

}

Необходимо объявить print() виртуальной функцией в абстрактном базовом Query, иначе мы не сможем вызвать ее для членов классов AndQury, OrQuery и NotQuery, являющихся указателями на операнды соответствующих запросов типа Query*. Однако для самого Query разумной реализации print() не существует. Поэтому мы определим ее как пустую функцию, а потом сделаем чисто виртуальной:

class Query {

public:

virtual ostream& print( ostream &os=cout ) const {}

// ...

};

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

// ошибка: ключевое слово virtual может появляться

// только в определении класса

virtual ostream& Query::print( ostream& ) const { ... }

Правильный вариант не должен включать слово virtual.

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

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

fiery && bird || shyly

пользователь ищет вхождения пары слов

fiery bird

или одного слова

shyly

С другой стороны, запрос

fiery && ( bird || hair )

найдет все вхождения любой из пар

fiery bird

или

fiery hair

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