public: // ...
protected:
virtual ~Query();
// ...
};
class NotQuery : public Query {
public:
~NotQuery();
// ...
};
уровень доступа к конструктору NotQuery открытый при вызове через объект NotQuery, но защищенный - при вызове через указатель или ссылку на объект Query. Таким образом, виртуальная функция подразумевает уровень доступа того класса, через объект которого вызывается:
int main()
{
Query *pq = new NotQuery;
// ошибка: деструктор является защищенным
delete pq;
}
Эвристическое правило: если в корневом базовом классе иерархии объявлены одна или несколько виртуальных функций, рекомендуем объявлять таковым и деструктор. Однако, в отличие от конструктора базового класса, его деструктор не стоит делать защищенным.
17.5.6. Виртуальная функция eval()
В основе иерархии классов Query лежит виртуальная функция eval() (но с точки зрения возможностей языка она наименее интересна). Как и для других функций-членов, разумной реализации eval() в абстрактном классе Query нет, поэтому мы объявляем ее чисто виртуальной:
class Query {
public:
virtual void eval() = 0;
// ...
};
Реальное разрешение имени eval() происходит при построении отображения слов на вектор позиций. Если слово есть в тексте, то в отображении будет его вектор позиций. В нашей реализации вектор позиций, если он имеется, передается конструктору NameQuery вместе с самим словом. Поэтому в классе NameQuery функция eval() пуста.
Однако мы не можем унаследовать чисто виртуальную функцию из Query. Почему? Потому что NameQuery - это конкретный класс, объекты которого разрешается создавать в приложении. Если бы мы унаследовали чисто виртуальную функцию, то он стал бы абстрактным классом, так что создать объект такого типа не удалось бы. Поэтому мы объявим eval() пустой функцией:
class NameQuery : public Query {
public:
virtual void eval() {}
// ...
};
Для запроса NotQuery отыскиваются все строки текста, где указанное слово отсутствует. Для таких строк в член _loc класса NotQuery помещаются все пары (строка, колонка). Наша реализация выглядит следующим образом:
void NotQuery::eval()
{
// вычислим операнд
_op-eval();
// _all_locs - это вектор, содержащий начальные позиции всех слов,
// он является статическим членом NotQuery:
// static const vectorlocations* _all_locs
vector location ::const_iterator
iter = _all_locs-begin(),
iter_end = _all_locs-end();
// получить множество строк, в которых операнд встречается
setshort *ps = _vec2set( _op-locations() );
// для каждой строки, где операнд не найден,
// скопировать все позиции в _loc
for ( ; iter != iter_end; ++iter )
{
if ( ! ps-count( (*iter).first )) {
_loc.push_back( *iter );
}
}
}
Ниже приводится трассировка выполнения запроса NotQuery. Операнд встречается в 0, 3 и 5 строках текста. (Напомним, что внутри программы строки текста в векторе нумеруются с 0; а когда мы предъявляем строки пользователю, мы нумеруем их с единицы.) Поэтому при вычислении ответа создается вектор, содержащий начальные позиции слов в строках 1,2 и 4. (Мы отредактировали вектор позиций, чтобы он занимал меньше места.)
== ! daddy
daddy ( 3 ) lines match
display_location_vector:
first: 0 second: 8
first: 3 second: 3
first: 5 second: 5
! daddy ( 3 ) lines match
display_location_vector:
first: 1 second: 0
first: 1 second: 1
first: 1 second: 2
...
first: 1 second: 10
first: 2 second: 0
first: 2 second: 1
...
first: 2 second: 12
first: 4 second: 0
first: 4 second: 1
...
first: 4 second: 12
Requested query: ! daddy
( 2 ) when the wind blows through her hair, it looks almost alive,
( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,
( 5 ) she tells him, at the same time wanting him to tell her more.
При обработке запроса OrQuery векторы позиций обоих операндов объединяются. Для этого применяется обобщенный алгоритм merge(). Чтобы merge() мог упорядочить пары (строка, колонка), мы определяем объект-функцию для их сравнения. Ниже приведена наша реализация:
class less_than_pair {
public:
bool operator()( location loc1, location loc2 )
{
return (( loc1.first loc2.first ) ||
( loc1.first == loc2.first ) &&
( loc1.second loc2.second ));