Главная страница
Навигация по странице:

  • Рис. 18.1. Множественное наследование источников освещения

  • Рис. 18.2. Иерархия виртуального наследования iostream (упрощенная)

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница86 из 93
    1   ...   82   83   84   85   86   87   88   89   ...   93
    921
    void
    TextQuery::query_text()
    {
    /* локальные объекты:
    *
    * text: содержит все слова запроса
    * query_text: вектор для хранения пользовательского запроса
    * caps: фильтр для поддержки преобразования
    * прописных букв в строчные
    *
    * user_query: объект UserQuery, в котором инкапсулировано
    * собственно вычисление ответа на запрос
    */ string text; string caps( "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ); vector query_text;
    UserQuery user_query;
    // инициализировать статические члены UserQuery
    NotQuery::all_locs( text_locations->second );
    AndQuery::max_col( &line_cnt );
    UserQuery::word_map( word_map ); do {
    // удалить предыдущий запрос, если он был query_text.clear(); cout << "
    Введите запрос. Пожалуйста, разделяйте все его "
    << "
    элементы пробелами.\n"
    << "
    Запрос (или весь сеанс) завершается точкой ( . ).\n\n"
    << "==> ";
    /*
    * прочитать запрос из стандартного ввода,
    * преобразовать все заглавные буквы, после чего
    * упаковать его в query_text ...
    *
    * примечание: здесь производятся все действия по
    * обработке запроса, связанные собственно с текстом ...
    */ while( cin >> text )
    { if ( text == "." ) break; string::size_type pos = 0; while (( pos = text.find_first_of( caps, pos ))
    != string::npos ) text[pos] = tolower( text[pos] ); query_text.push_back( text );
    }
    // теперь у нас есть внутреннее представление запроса
    // обработаем его ... if ( ! query_text.empty() )
    {
    // передать запрос объекту UserQuery user_query.query( &query_text );
    // вычислить ответ на запрос
    // вернуть иерархию Query*
    // подробности см. в разделе 17.7
    // query - это член класса TextQuery типа Query* query = user_query.eval_query();
    // вычислить иерархию Query,
    // реализация описана в разделе 17.7 query->eval();
    // вывести ответ с помощью
    // функции-члена класса TextQuery display_solution();
    // вывести на терминал пользователя дополнительную
    // пустую строку cout << endl;

    С++ для начинающих
    922
    }
    Тестируя программу, мы применили ее к нескольким текстам. Первым стал короткий рассказ Германа Мелвилла “Bartleby”. Здесь иллюстрируется составной запрос AndQuery, для которого подходящие слова расположены в соседних строках. (Отметим, что слова, заключенные между символами косой черты, предполагаются набранными курсивом.)
    Введите запрос. Пожалуйста, разделяйте все его элементы пробелами.
    Запрос (или весь сеанс) завершается точкой ( . ).
    ==> John && Jacob && Astor john ( 3 ) lines match jacob ( 3 ) lines match john && jacob ( 3 ) lines match astor ( 3 ) lines match john && jacob && astor ( 5 ) lines match
    Requested query: john && jacob && astor
    ( 34 ) All who know me consider me an eminently /safe/ man. The late
    John Jacob
    ( 35 ) Astor, a personage little given to poethic enthusiasm, had no hesitation in
    ( 38 ) my profession by the late John Jacob Astor, a name which, I admit
    I love to
    ( 40 ) bullion. I will freely add that I was not insensible to the late
    John Jacob
    ( 41 ) Astor's good opinion.
    Следующий запрос, в котором тестируются скобки и составные операторы, обращен к тексту новеллы “Heart of Darkness” Джозефа Конрада:
    ==> horror || ( absurd && mystery ) || ( North && Pole ) horror ( 5 ) lines match absurd ( 8 ) lines match mystery ( 12 ) lines match
    ( absurd && mystery ) ( 1 ) lines match horror || ( absurd && mystery ) ( 6 ) lines match north ( 2 ) lines match pole ( 7 ) lines match
    ( north && pole ) ( 1 ) lines match horror || ( absurd && mystery ) || ( north && pole )
    ( 7 ) lines match
    Requested query: horror || ( absurd && mystery ) || ( north && pole )
    ( 257 ) up I will go there.' The North Pole was one of these
    ( 952 ) horros. The heavy pole had skinned his poor nose
    ( 3055 ) some lightless region of subtle horrors, where pure,
    ( 3673 ) " 'The horror! The horror!'
    ( 3913 ) the whispered cry, 'The horror! The horror! '
    ( 3957 ) absurd mysteries not fit for a human being to behold.
    ( 4088 ) wind. 'The horror! The horror!'
    Последний запрос был обращен к отрывку из романа Генри Джеймса “Portrait of a Lady”.
    В нем иллюстрируется составной запрос в применении к большому текстовому файлу:
    ==> clever && trick || devious clever ( 46 ) lines match trick ( 12 ) lines match clever && trick ( 2 ) lines match devious ( 1 ) lines match clever && trick || devious ( 3 ) lines match
    Requested query: clever && trick || devious
    ( 13914 ) clever trick she had guessed. Isabel, as she herself grew older

    С++ для начинающих
    923
    ( 13935 ) lost the desire to know this lady's clever trick. If she had
    ( 14974 ) desultory, so devious, so much the reverse of processional.
    There were
    Упражнение 17.23
    Реализованная нами обработка запроса пользователя обладает одним недостатком: она не применяет к каждому слову те же предварительные фильтры, что и программа, строящая вектор позиций (см. разделы 6.9 и 6.10). Например, пользователь, который хочет найти слово “maps”, обнаружит, что в нашем представлении текста распознается только “map”, поскольку существительные во множественном числе приводятся к форме в единственном числе. Модифицируйте функцию query_text() так, чтобы она применяла эквивалентные фильтры к словам запроса.
    Упражнение 17.24
    Поисковую систему можно было бы усовершенствовать, добавив еще одну разновидность запроса “И”, которую мы назовем InclusiveAndQuery и будем обозначать символом &.
    Строка текста удовлетворяет условиям запроса, если в ней находятся оба указанных слова, пусть даже не рядом. Например, строка
    We were her pride of ten, she named us удовлетворяет запросу: pride & ten но не: pride && ten
    Поддержите запрос InclusiveAndQuery.
    Упражнение 17.25
    Представленная ниже реализация функции display_solution() может выводить только в стандартный вывод. Более правильно было бы позволить пользователю самому задавать поток ostream, в который надо направить вывод. Модифицируйте display_solution()
    так, чтобы ostream можно было задавать. Какие еще изменения необходимо внести в определение класса UserQuery?

    С++ для начинающих
    924
    }
    Упражнение 17.26
    Нашему классу TextQuery не хватает возможности принимать аргументы, заданные пользователем в командной строке.
    (a) Предложите синтаксис командной строки для нашей поисковой системы.
    (b) Добавьте в класс необходимые данные и функции-члены.
    (c) Предложите средства для работы с командной строкой (см. пример в разделе 7.8).
    Упражнение 17.27
    В качестве темы для рабочего проекта рассмотрите следующие усовершенствования нашей поисковой системы:
    (a) Реализуйте поддержку, необходимую для представления запроса AndQuery в виде одной строки, например “Motion Picture Screen Cartoonists”.
    (b) Реализуйте поддержку для ответа на запрос на основе вхождения слов не в строку, а в предложение.
    (c) Реализуйте подсистему хранения истории, с помощью которой пользователь мог бы ссылаться на предыдущий запрос по номеру, возможно, комбинируя его с новым запросом.
    (d) Вместо того чтобы показывать счетчик найденных и все найденные строки, реализуйте возможность задать диапазон выводимых строк для промежуточных вычислений и для окончательного ответа:
    ==> John && Jacob && Astor void TextQuery:: display_solution()
    { cout << "\n"
    << "Requested query: "
    << *query << "\n\n"; const set,allocator> *solution = query-
    >solution(); if ( ! solution->size() ) { cout << "\n\t"
    << "Sorry, no matching lines were found in text.\n"
    << endl;
    } set::const_iterator it = solution->begin(), end_it = solution->end(); for ( ; it != end_it; ++it ) { int line = *it;
    // пронумеруем строки с 1 ... cout << "( " << line+1 << " ) "
    << (*lines_of_text)[line] << '\n';
    } cout << endl;

    С++ для начинающих
    925
    (1) john ( 3 ) lines match
    (2) jacob ( 3 ) lines match
    (3) john && jacob ( 3 ) lines match
    (4) astor ( 3 ) lines match
    (5) john && jacob && astor ( 5 ) lines match
    //
    Новая возможность: пусть пользователь укажет, какой запрос выводить
    // пользователь вводит число
    ==> вывести? 3
    //
    Затем система спрашивает, сколько строк выводить
    // при нажатии клавиши Enter выводятся все строки,
    // но пользователь может также ввести номер одной строки или диапазон
    ð сколько (Enter выводит все, иначе введите номер строки или диапазон)
    1-3

    С++ для начинающих
    926
    18
    18.
    Множественное и виртуальное
    наследование
    В большинстве реальных приложений на C++ используется открытое наследование от одного базового класса. Можно предположить, что и в наших программах оно в основном будет применяться именно так. Но иногда одиночного наследования не хватает, потому что с его помощью либо нельзя адекватно смоделировать абстракцию предметной области, либо получающаяся модель чересчур сложна и неинтуитивна. В таких случаях следует предпочесть множественное наследование или его частный случай – виртуальное наследование. Их поддержка, имеющаяся в
    C++, – основная тема настоящей главы.
    18.1.
    Готовим сцену
    Прежде чем детально описывать множественное и виртуальное наследование, покажем, зачем оно нужно. Наш первый пример взят из области трехмерной компьютерной графики. Но сначала познакомимся с предметной областью.
    В компьютере сцена представляется графом сцены, который содержит информацию о геометрии (трехмерные модели), один или более источников освещения (иначе сцена будет погружена во тьму), камеру (без нее мы не можем смотреть на сцену) и несколько трансформационных узлов, с помощью которых позиционируются элементы.
    Процесс применения источников освещения и камеры к геометрической модели для получения двумерного изображения, отображаемого на дисплее, называется рендерингом.
    В алгоритме рендеринга учитываются два основных аспекта: природа источника освещения сцены и свойства материалов поверхностей объектов, такие, как цвет, шероховатость и прозрачность. Ясно, что перышки на белоснежных крыльях феи выглядят совершенно не так, как капающие из ее глаз слезы, хотя те и другие освещены одним и тем же серебристым светом.
    Добавление объектов к сцене, их перемещение, игра с источниками освещения и геометрией – работа компьютерного художника. Наша задача – предоставить интерактивную поддержку для манипуляций с графом сцены на экране. Предположим, что в текущей версии своего инструмента мы решили воспользоваться каркасом приложений Open Inventor для C++ (см. [WERNECKE94]), но с помощью подтипизации расширили его, создав собственные абстракции нужных нам классов. Например, Open
    Inventor располагает тремя встроенными источниками освещения, производными от абстрактного базового класса SoLight: class SoDirectionalLight : public SoLight { ... }
    Префикс So служит для того, чтобы дать уникальные имена сущностям, которые в области компьютерной графики весьма распространены (данный каркас приложений class SoSpotLight : public SoLight { ... } class SoPointLight : public SoLight { ... }

    С++ для начинающих
    927
    проектировался еще до появления пространств имен). Точечный источник (point light) – это источниксвета, излучающий, как солнце, во всех направлениях. Направленный
    источник (directional light) – источниксвета, излучающий в одном направлении.
    Прожектор (spotlight) – источник, испускающий узконаправленный конический пучок, как обычный театральный прожектор.
    По умолчанию Open Inventor осуществляет рендеринг графа сцены на экране с помощью библиотеки OpenGL (см. [NEIDER93]). Для интерактивного отображения этого достаточно, но почти все изображения, сгенерированные для киноиндустрии, сделаны с помощью средства RenderMan (см. [UPSTILL90]). Чтобы добавить поддержку такого алгоритма рендеринга мы, в частности, должны реализовать собственные специальные подтипы источников освещения: class RiDirectionalLight : public SoDirectionalLight { ... }
    Новые подтипы содержат дополнительную информацию, необходимую для рендеринга с помощью RenderMan. При этом базовые классы Open Inventor по-прежнему позволяют выполнять рендеринг с помощью OpenGL. Неприятности начинаются, когда возникает необходимость расширить поддержку теней.
    В RenderMan направленный источник и прожектор поддерживают отбрасывание тени
    (поэтому мы называем их источниками освещения, дающими тень, – SCLS), а точечный – нет. Общий алгоритм требует, чтобы мы обошли все источники освещения на сцене и составили карту теней для каждого включенного SCLS. Проблема в том, что источники освещения хранятся в графе сцены как полиморфные объекты класса SoLight. Хотя мы можем инкапсулировать общие данные и необходимые операции в класс SCLS, непонятно, как включить его в существующую иерархию классов Open Inventor.
    В поддереве с корнем SoLight в иерархии Open Inventor нет такого класса, из которого можно было бы произвести с помощью одиночного наследования класс SCLS так, чтобы в дальнейшем уже от него произвести SdRiSpotLight и SdRiDirectionalLight. Если не пользоваться множественным наследованием, лучшее, что можно сделать, – это сравнить член класса SCLS с каждым возможным типом SCLS-источника и вызвать соответствующую операцию:
    // и так далее
    (Оператор dynamic_cast – это часть механизма идентификации типов во время выполнения (RTTI). Он позволяет опросить тип объекта, адресованного полиморфным указателем или ссылкой. Подробно RTTI будет обсуждаться в главе 19.) class RiSpotLight : public SoSpotLight { ... } class RiPointLight : public SoPointLight { ... }
    SoLight *plight = next_scene_light(); if ( RiDirectionalLight *pdilite = dynamic_cast( plight )) pdilite->scls.cast_shadow_map(); else if ( RiSpotLight *pslite = dynamic_cast( plight )) pslite->scls.cast_shadow_map();

    С++ для начинающих
    928
    Пользуясь множественным наследованием, мы можем инкапсулировать подтипы SCLS, защитив наш код от изменений при добавлении или удалении источника освещения (см. рис. 18.1).
    SoNode SCLS
    SoLight
    SoPointLight SoSpotLight SoDirectionalLight
    RPointLight RSpotLight RDirectionalLight
    Рис. 18.1. Множественное наследование источников освещения
    pscls->cast_shadow_map();
    Это решение несовершенно. Если бы у нас был доступ к исходным текстам Open Inventor, то можно было бы избежать множественного наследования, добавив к SoLight член- указатель на SCLS и поддержку операции cast_shadow_map(): plight-> cast_shadow_map(); class RiDirectionalLight : public SoDirectionalLight, public SCLS { ... }; class RiSpotLight : public SoSpotLight, public SCLS { ... };
    // ...
    SoLight *plight = next_scene_light(); if ( SCLS *pscls = dynamic_cast(plight)) class SoLight : public SoNode { public: void cast_shadow_map()
    { if ( _scls ) _scls->cast_shadow_map(); }
    // ... protected:
    SCLS *_scls;
    };
    // ...
    SdSoLight *plight = next_scene_light();

    С++ для начинающих
    929
    Самое распространенное приложение, где используется множественное (и виртуальное) наследование, – это потоковая библиотека ввода/вывода в стандартном C++. Два основных видимых пользователю класса этой библиотеки – istream (для ввода) и ostream
    (для вывода). В число их общих атрибутов входят:

    информация о форматировании (представляется ли целое число в десятичной, восьмеричной или шестнадцатеричной системе счисления, число с плавающей точкой – в нотации с фиксированной точкой или в научной нотации и т.д.);

    информация о состоянии (находится ли потоковый объект в нормальном или ошибочном состоянии и т.д.);

    информация о параметрах локализации (отображается ли в начале даты день или месяц и т.д.);

    буфер, где хранятся данные, которые нужно прочитать или записать.
    Эти общие атрибуты вынесены в абстрактный базовый класс ios, для которого istream и ostream являются производными.
    Класс iostream – наш второй пример множественного наследования. Он предоставляет поддержку для чтения и записи в один и тот же файл; его предками являются классы istream и ostream. К сожалению, по умолчанию он также унаследует два различных экземпляра базового класса ios, а нам это не нужно.
    Виртуальное наследование решает проблему наследования нескольких экземпляров базового класса, когда нужен только один разделяемый экземпляр. Упрощенная иерархия iostream изображена на рис. 18.2. ios istream ostream ifstream iostream ofstream fstream
    Рис. 18.2. Иерархия виртуального наследования iostream (упрощенная)
    Еще один реальный пример виртуального и множественного наследования дают распределенные объектные вычисления. Подробное рассмотрение этой темы см. в серии статей Дугласа Шмидта (Douglas Schmidt) и Стива Виноски (Steve Vinoski) в
    [LIPPMAN96b].
    В данной главе мы рассмотрим использование и поведение механизмов виртуального и множественного наследования. В другой нашей книге, “Inside the C++ Object Model”, описаны более сложные вопросы производительности и дизайна этого аспекта языка.
    Для последующего обсуждения мы выбрали иерархию животных в зоопарке. Наши животные существуют на разных уровнях абстракции. Есть, конечно, особи, имеющие

    С++ для начинающих
    930
    свои имена: Линь-Линь, Маугли или Балу. Каждое животное принадлежит к какому-то виду; скажем, Линь-Линь – это гигантская панда. Виды в свою очередь входят в семейства. Так, гигантская панда – член семейства медведей, хотя, как мы увидим в разделе 18.5, по этому поводу в зоологии долго велись бурные дискуссии. Каждое семейство – член животного мира, в нашем случае ограниченного территорией зоопарка.
    На каждом уровне абстракции имеются данные и операции, необходимые для поддержки все более и более широкого круга пользователей. Например, абстрактный класс
    ZooAnimal хранит информацию, общую для всех животных в зоопарке, и предоставляет открытый интерфейс для всех возможных запросов.
    Помимо классов, описывающих животных, есть и вспомогательные классы, инкапсулирующие различные абстракции иного рода, например “животные, находящиеся под угрозой вымирания”. Наша реализация класса Panda множественно наследует от
    Bear
    (медведь) и Endangered (вымирающие).
    18.2.
    Множественное наследование
    Для поддержки множественного наследования синтаксис списка базовых классов class Bear : public ZooAnimal { ... }; расширяется: допускается наличие нескольких базовых классов, разделенных запятыми: class Panda : public Bear, public Endangered { ... };
    Для каждого из перечисленных базовых классов должен быть указан уровень доступа: public
    , protected или private. Как и при одиночном наследовании, множественно наследовать можно только классу, определение которого уже встречалось ранее.
    Язык не накладывает никаких ограничений на число базовых классов, которым может наследовать производный. На практике чаще всего встречается два класса, один из которых представляет открытый абстрактный интерфейс, а второй – закрытую реализацию (хотя ни один из рассмотренных выше примеров этой модели не следует).
    Производные классы, наследующие от трех или более базовых, – это пример такого стиля проектирования, когда каждый базовый класс представляет одну грань полного интерфейса производного.
    В случае множественного наследования объект производного класса содержит по одному подобъекту каждого из своих базовых (см. раздел 17.3). Например, когда мы пишем
    Panda ying_yang; то объект ying_yang будет состоять из подобъекта класса Bear (который в свою очередь содержит подобъект ZooAnimal), подобъекта Endangered и нестатических членов, объявленных в самом классе Panda, если таковые есть (см. рис. 18.3).
    ZooAnimal Endangered
    Bear

    С++ для начинающих
    1   ...   82   83   84   85   86   87   88   89   ...   93


    написать администратору сайта