17.7.1. Определение класса UserQuery
17.7.1. Определение класса UserQuery
Объект класса UserQuery можно инициализировать указателем на вектор строк, представляющий запрос пользователя, или передать ему адрес этого вектора позже, с помощью функции-члена query(). Это позволяет использовать один объект для нескольких запросов. Фактическое построение иерархии классов Query выполняется функцией eval_query():
// определить объект, не имея запроса пользователя
UserQuery user_query;
string text;
vectorstring query_text;
// обработать запросы пользователя
do {
while( cin text )
query_text.push_back( text );
// передать запрос объекту UserQuery
user_query.query( &query_text );
// вычислить результат запроса и вернуть
// корень иерархии Query*
Query *query = user_query.eval_query();
}
while ( /* пользователь продолжает формулировать запросы */ );
Вот определение нашего класса UserQuery:
#ifndef USER_QUERY_H
#define USER_QUERY_H
#include string
#include vector
#include map
#include stack
typedef pairshort,short location;
typedef vectorlocation,allocator loc;
#include quot;Query.h"t;
class UserQuery {
public:
UserQuery( vector string,allocator *pquery = 0 )
: _query( pquery ), _eval( 0 ), _paren( 0 ) {}
Query *eval_query(); // строит иерархию
void query( vector string,allocator *pq );
void displayQuery();
static void word_map( mapstring,loc*,lessstring,allocator *pwm ) {
if ( !_word_map ) _word_map = pwm;
}
private:
enum QueryType { WORD = 1, AND, OR, NOT, RPAREN, LPAREN };
QueryType evalQueryString( const string &query );
void evalWord( const string &query );
void evalAnd();
void evalOr();
void evalNot();
void evalRParen();
bool integrity_check();
int _paren;
Query *_eval;
vectorstring *_query;
stackQuery*, vectorQuery* _query_stack;
stackQuery*, vectorQuery* _current_op;
static short _lparenOn, _rparenOn;
static mapstring,loc*,lessstring,allocator *_word_map;
};
#endif
Обратите внимание, что два объявленных нами стека содержат указатели на объекты типа Query, а не сами объекты. Хотя правильное поведение обеспечивается обеими реализациями, хранение объектов значительно менее эффективно, поскольку каждый объект (и его операнды) должен быть почленно скопирован в стек (напомним, что операнды копируются виртуальной функцией clone()) только для того, чтобы вскоре быть уничтоженным. Если мы не собираемся модифицировать объекты, помещаемые в контейнер, то хранение указателей на них намного эффективнее.
Ниже показаны реализации различных встроенных операций eval. Операции evalAnd() и evalOr() выполняют следующие шаги. Сначала объект извлекается из стека _query_stack (напомним, что для класса stack, определенного в стандартной библиотеке, это требует двух операций: top() для получения элемента и pop() для удаления его из стека). Затем из хипа выделяется память для объекта класса AndQuery или OrQuery, и указатель на него передается объекту, извлеченному из стека. Каждая операция передает объекту AndQuery или OrQuery счетчики левых или правых скобок, необходимые ему для вывода своего содержимого. И наконец неполный оператор помещается в стек _current_op:
inline void
UserQuery::
evalAnd()
{
Query *pop = _query_stack.top(); _query_stack.pop();
AndQuery *pq = new AndQuery( pop );
if ( _lparenOn )
{ pq-lparen( _lparenOn ); _lparenOn = 0; }
if ( _rparenOn )
{ pq-rparen( _rparenOn ); _rparenOn = 0; }
_current_op.push( pq );
}
inline void
UserQuery::
evalOr()
{
Query *pop = _query_stack.top(); _query_stack.pop();
OrQuery *pq = new OrQuery( pop );
if ( _lparenOn )
{ pq-lparen( _lparenOn ); _lparenOn = 0; }
if ( _rparenOn )
{ pq-rparen( _rparenOn ); _rparenOn = 0; }
_current_op.push( pq );
}
Операция evalNot() работает следующим образом. В хипе создается новый объект класса NotQuery, которому передаются счетчики левых и правых скобок для правильного отображения содержимого. Затем неполный оператор помещается в стек _current_op:
inline void
UserQuery::
evalNot()
{
NotQuery *pq = new NotQuery;
if ( _lparenOn )
{ pq-lparen( _lparenOn ); _lparenOn = 0; }
if ( _rparenOn )
{ pq-rparen( _rparenOn ); _rparenOn = 0; }
_current_op.push( pq );
}
При обнаружении закрывающей скобки вызывается операция evalRParen(). Если число активных левых скобок больше числа элементов в стеке _current_op, то ничего не происходит. В противном случае выполняются следующие действия. Из стека _query_stack извлекается текущий еще не присоединенный к оператору операнд, а из стека _current_op - текущий неполный оператор. Вызывается виртуальная функция add_op() класса Query, которая их объединяет. И наконец полный оператор помещается в стек _query_stack:
inline void
UserQuery::
evalRParen()
{
if ( _paren _current_op.size() )
{
Query *poperand = _query_stack.top();
_query_stack.pop();
Query *pop = _current_op.top();
_current_op.pop();
pop-add_op( poperand );
_query_stack.push( pop );
}
}
Операция evalWord() выполняет следующие действия. Она ищет указанное слово в отображении _word_map взятых из файла слов на векторы позиций. Если слово найдено, берется его вектор позиций и в хипе посредством конструктора с двумя параметрами создается новый объект NameQuery. В противном случае объект порождается с помощью конструктора с одним параметром. Если число элементов в стеке _current_op меньше либо равно числу встреченных ранее скобок, то нет неполного оператора, ожидающего операнда типа NameQuery, поэтому новый объект помещается в стек _query_stack. Иначе из стека _current_op извлекается неполный оператор, к которому с помощью виртуальной функции add_op() присоединяется операнд NameQuery, после чего ставший полным оператор помещается в стек _query_stack:
inline void
UserQuery::
evalWord( const string &query )
{
NameQuery *pq;
loc *ploc;
if ( ! _word_map-count( query ))
pq = new NameQuery( query );
else {
ploc = ( *_word_map )[ query ];
pq = new NameQuery( query, *ploc );
}
if ( _current_op.size() = _paren )
_query_stack.push( pq );
else {
Query *pop = _current_op.top();
_current_op.pop();
pop-add_op( pq );
_query_stack.push( pop );
}
}
Упражнение 17.21
Напишите деструктор, копирующий конструктор и копирующий оператор присваивания для класса UserQuery.
Упражнение 17.22
Напишите функции print() для класса UserQuery. Обоснуйте свой выбор того, что она выводит.