<< Пред.           стр. 85 (из 121)           След. >>

Список литературы по разделу

 Поведение деструктора при наследовании таково: сначала вызывается деструктор производного класса, в случае pq – виртуальная функция. По завершении вызывается деструктор непосредственного базового класса – статически. Если деструктор объявлен встроенным, то в точке вызова производится подстановка. Например, если pq указывает на объект класса AndQuery, то
 delete pq;
 приводит к вызову деструктора класса AndQuery за счет механизма виртуализации. После этого статически вызывается деструктор BinaryObject, а затем – снова статически – деструктор Query.
 В следующей иерархии классов
 class Query {
 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 vector* _all_locs
  vector< location >::const_iterator
  iter = _all_locs->begin(),
  iter_end = _all_locs->end();
 
  // получить множество строк, в которых операнд встречается
  set *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 ));
  }
 };
 void OrQuery::eval()
 {
  // вычислить левый и правый операнды
  _lop->eval();
  _rop->eval();
 
  // подготовиться к объединению двух векторов позиций
  vector< location, allocator >::const_iterator
  riter = _rop->locations()->begin(),
  liter = _lop->locations()->begin(),
  riter_end = _rop->locations()->end(),
  liter_end = _lop->locations()->end();
 
  merge( liter, liter_end, riter, riter_end,
  inserter( _loc, _loc.begin() ),
  less_than_pair() );
 }
 А вот трассировка выполнения запроса OrQuery, в которой мы выводим вектор позиций каждого из двух операндов и результат их объединения. (Напомним еще раз, что для пользователя строки нумеруются с 1, а внутри программы – с 0.)
 
 ==> fiery || untamed
 fiery ( 1 ) lines match
 display_location vector:
  first: 2 second: 2
  first: 2 second: 8
 
 untamed ( 1 ) lines match
 display_location vector:
  first: 3 second: 2
 
 fiery || untamed ( 2 ) lines match
 display_location vector:
  first: 2 second: 2
  first: 2 second: 8
  first: 3 second: 2
 
 Requested query: fiery || untamed
 ( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,
 ( 4 ) magical but untamed. "Daddy, shush, there is no such thing,"
 
 При обработке запроса AndQuery мы обходим векторы позиций обоих операндов и ищем соседние слова. Каждая найденная пара вставляется в вектор _loc. Основная трудность связана с тем, что эти векторы нужно просматривать синхронно, чтобы можно было установить соседство слов.
 void AndQuery::eval()
 {
  // вычислить левый и правый операнды
  _lop->eval();
  _rop->eval();
 
  // установить итераторы
  vector< location, allocator >::const_iterator
  riter = _rop->locations()->begin(),
  liter = _lop->locations()->begin(),
  riter_end = _rop->locations()->end(),
  liter_end = _lop->locations()->end();
 
  // продолжать цикл, пока есть что сравнивать
  while ( liter != liter_end &&
  riter != riter_end )
  {
  // пока номер строки в левом векторе больше, чем в правом
  while ( (*liter).first > (*riter).first )
  {
  ++riter;
  if ( riter == riter_end ) return;
  }
 
  // пока номер строки в левом векторе меньше, чем в правом
  while ( (*liter).first < (*riter).first )
  {
  // если соответствие найдено для последнего слова
  // в одной строке и первого слова в следующей
  // _max_col идентифицирует последнее слово в строке
  if ( ((*liter).first == (*riter).first-1 ) &&
  ((*riter).second == 0 ) &&
  ((*liter).second == (*_max_col)[ (*liter).first ] ))
  {
  _loc.push_back( *liter );
  _loc.push_back( *riter );
  ++riter;
  if ( riter == riter_end ) return;
  }
  ++liter;
  if ( liter == liter_end ) return;
  }
 
  // пока оба в одной и той же строке
  while ( (*liter).first == (*riter).first )
  {
  if ( (*liter).second+1 == ((*riter).second) )
  { // соседние слова
  _loc.push_back( *liter ); ++liter;
  _loc.push_back( *riter ); ++riter;
  }
  else
  if ( (*liter).second <= (*riter).second )
  ++liter;
  else ++riter;
  if ( liter == liter_end || riter == riter_end )
  return;
  }
  }
 }
 А так выглядит трассировка выполнения запроса AndQuery, в которой мы выводим векторы позиций обоих операндов и результирующий вектор:
 
 ==> fiery && bird
 fiery ( 1 ) lines match
 display_location vector:
  first: 2 second: 2
  first: 2 second: 8
 bird ( 1 ) lines match
 display_location vector:
  first: 2 second: 3
  first: 2 second: 9
 fiery && bird ( 1 ) lines match
 display_location vector:
  first: 2 second: 2
  first: 2 second: 3
  first: 2 second: 8
  first: 2 second: 9
 
 Requested query: fiery && bird
 ( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,
 
 Приведем трассировку выполнения составного запроса, включающего как И, так и ИЛИ. Показаны векторы позиций каждого операнда, а также результирующий вектор:
 
 ==> fiery && ( bird || untamed )
 fiery ( 1 ) lines match
 display_location vector:
  first: 2 second: 3
  first: 2 second: 8
 bird ( 1 ) lines match
 display_location vector:
  first: 2 second: 3
  first: 2 second: 9
 untamed ( 1 ) lines match
 display_location vector:
  first: 3 second: 2
 ( bird || untamed ) ( 2 ) lines match
 display_location vector:
  first: 2 second: 3
  first: 2 second: 9
  first: 3 second: 2
 fiery && ( bird || untamed ) ( 1 ) lines match
 display_location vector:
  first: 2 second: 2
  first: 2 second: 3
  first: 2 second: 8
  first: 2 second: 9
 Requested query: fiery && ( bird || untamed )
 ( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,
 
 17.5.7. Почти виртуальный оператор new
 Если дан указатель на один из конкретных подтипов запроса, то разместить в хипе дубликат объекта несложно:
 NotQuery *pnq;
 // установить pnq ...
 
 // оператор new вызывает
 // копирующий конструктор NotQuery ...
 NotQuery *pnq2 = new NotQuery( *pnq );
 Если же у нас есть только указатель на абстрактный класс Query, то задача создания дубликата становится куда менее тривиальной:
 const Query *pq = pnq->op();
 // как получить дубликат pq?
 Если бы позволялось объявить виртуальный экземпляр оператора new, то проблема была бы решена, поскольку автоматически вызывался бы нужный экземпляр. К сожалению, это невозможно: new – статическая функция-член, которая применяется к неструктурированной памяти еще до конструирования объекта класса (см. раздел 15.8).
 Но хотя оператор new нельзя сделать виртуальным, разрешается создать его суррогат, который будет выделять память из хипа и копировать туда объекты, – clone():
 class Query {
 public:
  virtual Query *clone() = 0;
  // ...
 };
 Вот как он может быть реализован в классе NameQuery:
 class NameQuery : public Query {
 public:
  virtual Query *clone()
  // вызывается копирующий конструктор класса NameQuery
  { return new NameQuery( *this ); }
 
  // ...
 };
 Это работает правильно, если тип целевого указателя Query*:
 Query *pq = new NameQuery( "valery" );
 Query *pq2 = pq->clone();
 Если же его тип равен NameQuery*, нужно привести возвращенный указатель типа Query* назад к типу NameQuery*:
 NameQuery *pnq = new NameQuery( "Rilke" );
 NameQuery *pnq2 =
  static_cast( pnq->clone() );
 (Причина, по которой необходимо преобразование типа, объясняется в разделе 19.1.1.)
 Как правило, тип значения, возвращаемого реализацией виртуальной функции в производном классе, должен совпадать с типом, возвращаемым ее реализацией в базовом. Исключение, о котором мы уже упоминали, призвано поддержать рассмотренную ситуацию. Если виртуальная функция в базовом классе возвращает значение некоторого типа класса (либо указатель или ссылку на тип класса), то ее реализация в производном может возвращать значение, тип которого является производным от этого класса с открытым типом наследования (то же относится к ссылкам и указателям):
 class NameQuery : public Query {
 public:
  virtual NameQuery *clone()
  { return new NameQuery( *this ); }
 
 // ...
 };
 Теперь pq2 и pnq2 можно инициализировать без явного приведения типов:
 // Query *pq = new NameQuery( "Broch" );
 Query *pq2 = pq->clone(); // правильно
 
 // NameQuery *pnq = new NameQuery( "Rilke" );

<< Пред.           стр. 85 (из 121)           След. >>

Список литературы по разделу