Определение класса UserQuery
Объект класса UserQuery
можно инициализировать указателем на вектор строк, представляющий запрос пользователя, или передать ему адрес этого вектора позже, с помощью функции-члена query(). Это позволяет использовать один объект для нескольких запросов. Фактическое построение иерархии классов Query выполняется функцией eval_query():
// определить объект, не имея запроса пользователя UserQuery user_query; string text; vector<string> 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 pair<short,short> location; typedef vector<location,allocator> loc; #include "Query.h" 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( map<string,loc*,less<string>,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; vector<string> *_query; stack<Query*, vector<Query*> > _query_stack; stack<Query*, vector<Query*> > _current_op; static short _lparenOn, _rparenOn; static map<string,loc*,less<string>,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. Обоснуйте свой выбор того, что она выводит.